在C#中,信号是用于线程间同步的工具,允许线程通过信号进行等待和通知,以控制并发执行。以下是几种常见的信号工具及其用法:
1. ManualResetEvent
和 AutoResetEvent
这两个类都是基于信号量的同步原语。它们都允许线程等待某个信号并决定何时继续执行,区别在于事件被触发后的重置行为:
ManualResetEvent
:在调用Set()
方法后保持信号状态(有信号),除非手动调用Reset()
方法清除信号。AutoResetEvent
:调用Set()
后自动重置为无信号状态,只释放一个等待的线程。
示例:使用ManualResetEvent
using System;
using System.Threading;class Program
{static ManualResetEvent manualEvent = new ManualResetEvent(false); // 初始化为无信号static void Main(string[] args){Thread t = new Thread(Work);t.Start();Console.WriteLine("主线程等待一秒钟,然后发出信号...");Thread.Sleep(1000);// 主线程发出信号manualEvent.Set();Console.WriteLine("信号已发出,子线程可以继续执行...");}static void Work(){Console.WriteLine("子线程启动,等待信号...");manualEvent.WaitOne(); // 等待信号Console.WriteLine("子线程收到信号,继续执行任务...");}
}
注释:
ManualResetEvent(false)
初始化为无信号状态,表示线程在WaitOne()
处会阻塞。Set()
发出信号后,所有等待的线程都会继续执行。
示例:使用AutoResetEvent
using System;
using System.Threading;class Program
{static AutoResetEvent autoEvent = new AutoResetEvent(false); // 初始化为无信号static void Main(string[] args){Thread t = new Thread(Work);t.Start();Console.WriteLine("主线程等待一秒钟,然后发出信号...");Thread.Sleep(1000);// 主线程发出信号autoEvent.Set();Console.WriteLine("信号已发出,子线程可以继续执行...");}static void Work(){Console.WriteLine("子线程启动,等待信号...");autoEvent.WaitOne(); // 等待信号Console.WriteLine("子线程收到信号,继续执行任务...");}
}
注释:
AutoResetEvent
每次Set()
只释放一个等待的线程,且自动重置为无信号状态。
2. Semaphore
和 SemaphoreSlim
Semaphore
:用于限制可访问共享资源的线程数量。SemaphoreSlim
:是轻量版的Semaphore
,但提供了类似的功能,更适合单进程中的线程同步。
示例:使用SemaphoreSlim
来限制线程访问资源
using System;
using System.Threading;class Program
{static SemaphoreSlim semaphore = new SemaphoreSlim(2); // 允许最多两个线程同时访问资源static void Main(string[] args){for (int i = 0; i < 5; i++){int threadNum = i;Thread t = new Thread(() => AccessResource(threadNum));t.Start();}}static void AccessResource(int threadNum){Console.WriteLine($"线程 {threadNum} 尝试访问资源...");semaphore.Wait(); // 尝试获取信号量,如果已经有两个线程持有信号量,则阻塞Console.WriteLine($"线程 {threadNum} 获得了资源,正在处理...");Thread.Sleep(2000); // 模拟资源处理时间Console.WriteLine($"线程 {threadNum} 释放了资源...");semaphore.Release(); // 释放信号量,允许其他线程继续访问}
}
注释:
SemaphoreSlim(2)
允许最多两个线程同时访问资源,其余线程会等待。Wait()
方法阻塞线程,直到信号量可用。Release()
释放信号量,允许其他线程访问资源。
3. CountdownEvent
CountdownEvent
用于等待一组操作完成。它初始化时设置一个计数器,当多个线程执行完工作后调用Signal()
,减少计数器值,直到计数器归零时,所有等待线程被唤醒。
示例:使用CountdownEvent
using System;
using System.Threading;class Program
{static CountdownEvent countdown = new CountdownEvent(3); // 初始化计数器为3static void Main(string[] args){for (int i = 0; i < 3; i++){Thread t = new Thread(DoWork);t.Start();}Console.WriteLine("主线程等待所有子线程完成工作...");countdown.Wait(); // 等待计数器归零Console.WriteLine("所有子线程工作完成,主线程继续...");}static void DoWork(){Console.WriteLine("子线程开始工作...");Thread.Sleep(1000); // 模拟工作countdown.Signal(); // 通知工作完成,减少计数器Console.WriteLine("子线程完成工作...");}
}
注释:
CountdownEvent(3)
初始化时设置计数器为3,表示需要3个线程完成工作后主线程才会继续。- 每个线程在工作完成后调用
Signal()
,减少计数器,所有线程完成后countdown.Wait()
解除阻塞。
4. Barrier
Barrier
用于协调多个线程并发执行,在指定的“阶段”都完成时,所有线程一起继续下一阶段的执行。
示例:使用Barrier
来协调线程
using System;
using System.Threading;class Program
{static Barrier barrier = new Barrier(3, (b) =>{Console.WriteLine($"所有线程已完成第 {b.CurrentPhaseNumber + 1} 阶段。");});static void Main(string[] args){for (int i = 0; i < 3; i++){Thread t = new Thread(DoWork);t.Start();}}static void DoWork(){Console.WriteLine("线程开始第1阶段...");Thread.Sleep(1000); // 模拟工作barrier.SignalAndWait(); // 等待所有线程到达第1阶段Console.WriteLine("线程开始第2阶段...");Thread.Sleep(1000); // 模拟工作barrier.SignalAndWait(); // 等待所有线程到达第2阶段}
}
注释:
Barrier(3)
初始化时设置参与线程数为3。- 每个线程在阶段完成时调用
SignalAndWait()
,等待所有线程完成当前阶段后,继续执行下一阶段。
这些信号工具在多线程编程中非常有用,它们帮助开发者有效地协调和管理线程间的并发执行。