【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
image

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()方法。

posted @   徘徊彼岸花  阅读(315)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示