C#_线程同步基元之WaitHandle

一、前言

官方文档
WaitHandle位于System.Threading命名空间下,是用来封装等待对共享资源进行独占访问的操作系统特定的对象。WaitHandle是一个抽象类,我们一般不直接使用,而是使用它的派生类:

WaitHandle
EventWaitHandle
AutoResetEvent
ManualResetEvent
Mutex
Semaphore

二、AutoResetEvent

AutoResetEvent表示线程同步事件在一个等待线程释放后收到信号时自动重置。WaitOne()方法阻塞程序执行,Set()方法释放信息。当释放后阻塞的代码继续执行。但下一次执行还需要等待信号。通俗来说,WaitOne()是关门,Set()是开门。但是开门之后,下次执行到WaitOne()的时候还需要等待开门,因为它在开门之后会自动关门。

举例,两个线程交替打印奇数和偶数:

    class Program
    {
       static AutoResetEvent autoReset = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            Task.Run(() =>
            {
                for (int i = 1; i <= 100; i++)
                {
                    if (i % 2 == 0)
                    {
                        autoReset.WaitOne();
                        Console.WriteLine($"我是偶数:{i}");
                        autoReset.Set();
                    }   
                }
            });

            Task.Run(() =>
            {
                for (int i = 1; i <= 100; i++)
                {
                    if (i % 2 != 0)
                    {
                        Console.WriteLine($"我是奇数:{i}");
                        autoReset.Set();
                        autoReset.WaitOne();
                    }
                }
            });

            Console.ReadKey();
        }

原理:先打印奇数,所以在打印偶数之前需要阻塞 ,直到打印了奇数后释放。但是打印奇数释放后就不管了吗???很显然我们还要在打印了奇数后释放后再次阻塞,之后偶数打印完了才释放,这样就实现了交替打印。

注意,WaitOne()方法还有重载,有一个参数表示等待的毫秒数,如果为-1,则表示无限期等待。WaitOne()方法的返回值是一个bool类型,为true则表示等到了释放,为false则表示超过指定时间没有等待到释放。

抽象类WaitHandle还有静态方法WaitAll()和WaitAny()等,可以等待多个WaitHandle对象释放。

三、ManualResetEvent

ManualResetEvent与AutoResetEvent类似,唯一的区别就是:

AutoResetEvent在Set()之后,再次遇到WaitOne()的时候还是阻塞的。但是,如果阻塞超时后,才收到Set,那么在直接waitone的时候不会阻塞,需要重新reset

ManualResetEvent在Set()之后,再次遇到WaitOne()的时候可直接执行。必须Reset()之后,才会阻塞。

四、Semaphore

其实.NET中的信号量(Semaphore)是操作系统维持的一个整数。当整数位0时。其他线程无法进入。当整数大于0时,线程可以进入。每当一个线程进入,整数-1,线程退出后整数+1。整数不能超过信号量的最大请求数。信号量在初始化的时候可以指定这个整数的初始值。

    class Program
    {
        static void Main(string[] args)
        {
            Foo foo = new Foo();

            Console.ReadKey();
        }
    }

    public class Foo
    {
        Semaphore semaphore = new Semaphore(0, 3);//允许最大3个线程占用信号量 超过后就会在WaitOne那阻塞

        public Foo()
        {
            for (int i = 0; i < 5; i++)
            {
                int j = i;
                Task.Run(() => DoSomething(j));
            }
            //alling Release(3) brings the 
            // semaphore count back to its maximum value, and
            // allows the waiting threads to enter the semaphore,
            // up to three at a time
            semaphore.Release(3);
        }

        private void DoSomething(int i)
        {
            semaphore.WaitOne();//当前线程占用信号量  如果信号量被占满 则阻塞
            Console.WriteLine($"{i}进入信号量");

            Thread.Sleep(1000 * i);

            Console.WriteLine($"{i}释放信号量  信号量线程数量:{semaphore.Release()}");//当前线程释放信号量
        }
    }

五、SemaphoreSlim

信号量分为本地信号量(SemaphoreSlim)和系统信号量(Semaphore ),单应用程序内部同步建议使用SemaphoreSlim。

public class SemaphoreSlimTest
    {
        SemaphoreSlim semaphore = new SemaphoreSlim(0, 3);
        public SemaphoreSlimTest()
        {
            for (int i = 0; i < 5; i++)
            {
                int j = i;
                Task.Run(() => DoSomething(j));
                Thread.Sleep(j * 1000);
            }
            //alling Release(3) brings the 
            // semaphore count back to its maximum value, and
            // allows the waiting threads to enter the semaphore,
            // up to three at a time

            Thread.Sleep(500);
            Console.WriteLine("Main thread calls Release(3) ");
            semaphore.Release(3);
        }

        private void DoSomething(int i)
        {
            Console.WriteLine($"{i}等待进入信号量  CurrentCount:{semaphore.CurrentCount}");
            semaphore.Wait();//当前线程占用信号量 如果信号量被占满 则阻塞
            Console.WriteLine($"{i}进入信号量  CurrentCount:{semaphore.CurrentCount}");

            Thread.Sleep(1000*i);

            var count = semaphore.Release();
            Console.WriteLine($"{i}释放信号量  PreviousCount:{count}");//当前线程释放信号量
        }
    }

六、Mutex

Mutex对象除了可以同步对受保护资源的访问,还可用于进程间同步的同步基元。例如可以使用Mutex防止应用程序打开多次。

    class Program
    {
        static Mutex mutex = null;
        static void Main(string[] args)
        {
            mutex = new Mutex(true, "MM", out bool createdNew);
            if (createdNew)
            {
                Console.WriteLine("我是新开的!!!");
                Console.ReadKey();
            }
            else
            {
                Console.WriteLine("已经打开了一个!!!");
                Console.ReadKey();
            }
        }
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值