【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 @ 2023-05-15 15:56  徘徊彼岸花  阅读(161)  评论(0编辑  收藏  举报