【C#多线程】Semaphore/AutoResetEvent/ManualResetEvent
信号量原理:https://blog.csdn.net/challenglistic/article/details/124862942
https://blog.csdn.net/u013400314/article/details/126707542
Semaphore
https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.semaphore?view=net-7.0
信号量和互斥锁mutex很相似,但信号量允许多个线程共同访问某个共享资源,但限制同时访问的线程数。
本质上,信号量内部有个计数器,比如你想限制最多5个线程运行,那么这个计数器的值就会被设置成5,如果一个线程调用了这个Semaphore,那么它的计数器就会相应的减1,直到这个计数器变为0。这时,如果有另一个线程继续调用这个Semaphore,那么这个线程就会被阻塞。
获得Semaphore的线程处理完它的逻辑之后,你就可以调用它的Release()函数将它的计数器重新加1,这样其它被阻塞的线程就可以得到调用了。
//构造 public Semaphore (int initialCount, int maximumCount);
initialCount初始剩余的计数(初始可允许获取信号量的线程数)
maximumCount信号量计数的最大值,超过这个线程数其他线程想要获得(WaitOne)会阻塞。
常用方法
- Release
Release()等价于Release(1) //释放semaphore内部计数器1
- WaitOne
当前线程申请一个信号量计数,若信号量计数达到maximumCount则阻塞,直到其他线程Release一个计数。
示例
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class mythread { public Thread m_thread; static Semaphore m_semaphore = new Semaphore(2, 2); public mythread(string name) { m_thread = new Thread(this.run); m_thread.Name = name; m_thread.Start(); } void run() { Console.WriteLine(m_thread.Name + ":正在等待一个许可证..."); //申请一个许可证。若m_semaphore计数器为0了,则阻塞直到其他获取semaphore的线程Release了一个计数。 m_semaphore.WaitOne(); Console.WriteLine(m_thread.Name + ":申请到许可证..."); for (int i = 0; i < 4; i++) { Console.WriteLine(m_thread.Name + ":" + i); Thread.Sleep(1000); } Console.WriteLine(m_thread.Name + ":释放许可证..."); m_semaphore.Release(); //semaphore计数器释放1。 } } }
2.AutoSetEvent/ManualSetEvent
https://learn.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-7.0
public sealed class Semaphore : System.Threading.WaitHandle
阻塞调用WaitOne线程,直到AutoSetEvent对象调用Set()发送信号。这样被阻塞的线程会被唤醒。
AutoSetEvent和ManualSetEvent的区别在于:前者调用一次Set()后,又会立刻设置成false,所以只随机唤醒一个线程。后者除非手动设置Reset(),否则调用Set()设置为true后一直为true,会唤醒所有线程。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; using System.Threading; class MREDemo { private AutoResetEvent _mre; //private ManualResetEvent _mre; public MREDemo() { this._mre = new AutoResetEvent(true); //this._mre = new ManualResetEvent(true); } public void CreateThreads() { Thread t1 = new Thread(new ThreadStart(Run)); t1.Start(); Thread t2 = new Thread(new ThreadStart(Run)); t2.Start(); } public void Set() { this._mre.Set(); } public void Reset() { this._mre.Reset(); } private void Run() { string strThreadID = string.Empty; try { while (true) { // 阻塞当前线程 this._mre.WaitOne(); strThreadID = Thread.CurrentThread.ManagedThreadId.ToString(); Console.WriteLine("Thread(" + strThreadID + ") is running..."); Thread.Sleep(5000); } } catch (Exception ex) { Console.WriteLine("线程(" + strThreadID + ")发生异常!错误描述:" + ex.Message.ToString()); } } } class Program { static void Main(string[] args) { Console.WriteLine("****************************"); Console.WriteLine("输入\"stop\"停止线程运行..."); Console.WriteLine("输入\"run\"开启线程运行..."); Console.WriteLine("****************************\r\n"); MREDemo objMRE = new MREDemo(); objMRE.CreateThreads(); while (true) { string input = Console.ReadLine(); if (input.Trim().ToLower() == "stop") { Console.WriteLine("线程已停止运行..."); objMRE.Reset(); } else if (input.Trim().ToLower() == "run") { Console.WriteLine("线程开启运行..."); objMRE.Set(); } } } }
https://blog.csdn.net/m0_55413404/article/details/127318697
https://developer.aliyun.com/article/345919
//构造 //true to set the initial state signaled; false to set the initial state to nonsignaled. ManualResetEvent manualResetEvent = new ManualResetEvent(false); //初始设为非信号状态,WaitOne阻塞
在上面代码中,我们初始化了一个值为False的ManualResetEvent对象,这意味着所有调用WaitOne放的线程将被阻塞,直到有线程调用了 Set() 方法发信号。而如果我们用值True来对ManualResetEvent对象进行初始化,所有调用WaitOne方法的线程并不会被阻塞,可以进行后续的执行。
**对于AutoResetEvent,若初始设为true,即信号状态。则子线程执行到WaitOne由于获得信号不会阻塞,但遇到WaitOne会将信号立刻设为false,所以只会随机有1个线程被执行。自动设置为false以后的操作,和一开始设置为false相同,还是Set()置为信号状态发信号,Reset()重置为非信号状态。
对于ManualResetEvent,若初始设为true信号状态。子线程执行到WaitOne不阻塞,信号不会AutoReset为false。所以所有线程在WaitOne都不会阻塞,除非手动调用了Reset()。
public virtual bool WaitOne ();
阻塞当前调用线程等待直到当前WaitHandle收到信号才继续向下执行。
当WaitHandle收到信号返回true,否则一直阻塞。
public class Program { private static AutoResetEvent event_1 = new AutoResetEvent(true); private static AutoResetEvent event_2 = new AutoResetEvent(false); static void Main(string[] args) { Console.WriteLine("Press Enter to create three threads and start them.\r\n" + "The threads wait on AutoResetEvent #1, which was created\r\n" + "in the signaled state, so the first thread is released.\r\n" + "This puts AutoResetEvent #1 into the unsignaled state."); Console.ReadLine(); for (int i = 1; i < 4; i++) { Thread t = new Thread(ThreadProc); t.Name = "Thread_" + i; t.Start(); } Thread.Sleep(250); for (int i = 0; i < 2; i++) { Console.WriteLine("Press Enter to release another thread."); Console.ReadLine(); event_1.Set(); Thread.Sleep(250); } Console.WriteLine("\r\nAll threads are now waiting on AutoResetEvent #2."); for (int i = 0; i < 3; i++) { Console.WriteLine("Press Enter to release a thread."); Console.ReadLine(); event_2.Set(); Thread.Sleep(250); } } static void ThreadProc() { string name = Thread.CurrentThread.Name; //1 Console.WriteLine("{0} waits on AutoResetEvent #1.", name); event_1.WaitOne(); //2 Console.WriteLine("{0} is released from AutoResetEvent #1.", name); //3 Console.WriteLine("{0} waits on AutoResetEvent #2.", name); event_2.WaitOne(); //4 Console.WriteLine("{0} is released from AutoResetEvent #2.", name); //5 Console.WriteLine("{0} ends.", name); }
a,第一个For,三线程Thread_1,Thread_2,Thread_3依次执行ThreadProc()
i,Thread_1执行1后遇到 event_1.WaitOne(),此时 event_1=AutoResetEvent(true),为信号状态,所以不阻塞线程,执行2,3,同时转换为非信号状态。然后遇到event_2.WaitOne(),event_2=AutoResetEvent(false),为非信号状态,所以阻塞线程。不再执行。
注:此时Thread_1的4,5在等待event_2。
ii,Thread_2执行1后遇到 event_1.WaitOne(),因为i,可知event_1现在为非信号状态,所以阻塞线程。
注:此时 Thread_2的2,3,4,5在等待event_1。
iii,Thread_3执行1后遇到 event_1.WaitOne(),因为i,可知event_1现在为非信号状态,所以阻塞线程。
注:此时 Thread_3的2,3,4,5在等待event_1。
b,第二个For,循环两次
i,输出Press Enter to release another thread.后遇到event_1.Set();event_1释放一个线程,即Thread_2的2,3,4,5,执行2,3后遇到event_2.WaitOne,event_2为非信号状态,阻塞线程。
注:Thread_2的4,5在等待event_2。
ii,输出Press Enter to release another thread.后遇到event_1.Set();event_1又释放一个线程,即Thread_3的2,3,4,5,执行2,3后遇到event_2.WaitOne,event_2为非信号状态,阻塞线程。
注:Thread_3的4,5在等待event_2。
c,第三个For,循环3次
i,输出Press Enter to release a thread.后遇到event_2.Set();event_2释放一个线程,即Thread_1的4,5。
ii,输出Press Enter to release a thread.后遇到event_2.Set();event_2释放一个线程,即Thread_2的4,5。
ii,输出Press Enter to release a thread.后遇到event_2.Set();event_2释放一个线程,即Thread_3的4,5。
-
WaitOne(int milliseconds)
阻塞当前线程直到WaitHandle收到信号,或超时(毫秒),超时后当前线程继续向下执行。
若是WaitHandle收到信号的返回则返回true,超时返回false。 -
Set()/Reset()
EventWaitHandle类开始有的方法,WaitHandle类没有。
Set()置为信号状态发信号,Reset()置为非信号状态。
一旦我们调用了Set()方法,它的bool值就变为true信号状态,经过WaitOne获取信号后就会变为false非信号状态。我们可以调用Reset()方法来重置该值,Reset()方法重置该值为False。如果我们想多次发送信号,那么我们必须在调用Set()方法后立即调用Reset()方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)