Thread同步—锁、信号量

本文介绍了C#中的Monitor类和ReaderWriterLock类用于线程间的同步控制,以及Semaphore类控制线程执行数量的方法。Monitor类能确保临界区一次只被一个线程访问,而ReaderWriterLock支持多读少写的并发场景,Semaphore则限制了访问资源的线程数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、锁Monitor(控制每个线程之间通信的执行顺)
1. Monitor(控制线程间通信顺序)
说到锁的时候通常都会想到Lock,Lock确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要进行控制每个线程之间通信的执行顺序,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。
这里主要介绍Monitor类主要是锁定的临界区中只允许让一个线程访问,其他线程排队等待
Monitor.Enter()获取指定对象上的排他(锁住指定对象在当前线程访问期间,其他对象无法访问)
Monitor.Wait()暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁
Monitor.Pulse()唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁
Monitor.Exit()释放指定对象上的排他锁(释放锁住的对象允许其他对象访问)
看如下代码:

static void Main(string[] args)
        {
            Thread thread1 = new Thread(new ThreadStart(Run1));
            thread1.Start();
            Thread thread2 = new Thread(new ThreadStart(Run2));
            thread2.Start();
            Console.ReadLine();
        }
public static void Run1()
        {
            Monitor.Enter(obj);
            Console.WriteLine("Hello,Merry,I am Jack");
            Monitor.Wait(obj);
            Console.WriteLine("I Love You Merry");
            Monitor.Pulse(obj);
            Monitor.Wait(obj);
            Console.WriteLine("that is good Meery");
            Monitor.Pulse(obj);
            Monitor.Exit(obj);
        }
        public static void Run2()
        {
            Monitor.Enter(obj);
            Console.WriteLine("Hello,Jack,I am Merry");
            Monitor.Pulse(obj);
            Monitor.Wait(obj);
            Console.WriteLine("I also Love You Jack");
            Monitor.Pulse(obj);
            Monitor.Wait(obj);
            Console.WriteLine("Yes Jack");
            Monitor.Exit(obj);
        }

允许结果:
这里写图片描述

  1. ReaderWriterLock(实现多用户读/单用户写同步)
    C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。
    用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的并发访问,这样的效率总是不太高。许多时候,应用程序在访问资源时是进行读操作,写操作相对较少。为解决这一问题,C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。如果资源未被添加任何读或写操作锁,那么一个且仅有一个线程可对该资源添加写操作锁定,以写入数据。简单的讲就是:读操作锁是共享锁,允许多个线程同时读取数据;写操作锁是独占锁,同一时刻,仅允许一个线程进行写操作。
    看下面代码:
public class WirteReadLockHelper
    {
        //资源
        static int intCount = 0;
        //读、写操作锁
        static ReaderWriterLock rwl = new ReaderWriterLock();

        static void Main(string[] args)
        {
            //分别创建2个读操作线程,2个写操作线程,并启动
            Thread tr0 = new Thread(new ThreadStart(Read));
            Thread tr1 = new Thread(new ThreadStart(Read));
            Thread tr2 = new Thread(new ThreadStart(Write));
            Thread tr3 = new Thread(new ThreadStart(Write));
            tr0.Start();
            tr1.Start();
            tr2.Start();
            tr3.Start();
            System.Console.ReadKey();
        }

        //读数据
        static void Read()
        {

            //申请读操作锁,如果在1000ms内未获取读操作锁,则放弃
            rwl.AcquireReaderLock(1000);
            Console.WriteLine("开始读取数据,我读取的数据是:{0}", intCount);
            Thread.Sleep(10);
            Console.WriteLine("读取数据结束,我读取的数据是:{0}", intCount);
            //释放读操作锁
            rwl.ReleaseReaderLock();


        }

        //写数据
        static void Write()
        {
            //申请写操作锁,如果在1000ms内未获取写操作锁,则放弃
            rwl.AcquireWriterLock(1000);
            Console.WriteLine("开始写数据,我读取的数据是:{0}", intCount);
            //将theResource加1
            intCount++;
            Thread.Sleep(100);
            Console.WriteLine("写数据结束,我读取的数据是:{0}", intCount);
            //释放写操作锁
            rwl.ReleaseWriterLock();

        }
    }

运行结果:
这里写图片描述

观察运行结果,我们很容易看出:读操作锁是共享锁,允许多个线程同时读取数据;写操作锁是独占锁,仅允许一个线程进行写操作。

  如果一个线程在获取读操作锁后,进行读操作的途中,希望提升锁级别,将其变为写操作锁,可以调用ReaderWriterLock类的UpgradeToWriterLock(int timeOut)方法,该方法返回一个LockCookie值,该值保存了UpgradeToWriterLock方法调用前线程锁的状态。待写操作完成后,可调用DowngradeFromWriterLock(LockCookie lockcookie)方法,该方法根据传入的LockCookie参数值,将线程锁恢复到UpgradeToWriterLock方法调用前的状态。
  
二、信号量(Semaphore控制线程执行的数量)
信号量(Semaphore)是由内核对象维护的int变量,当信号量为0时,在信号量上等待的线程会堵塞,信号量大于0时,就解除堵塞。当在一个信号量上等待的线程解除堵塞时,内核自动会将信号量的计数减1。在.net 下通过Semaphore类来实现信号量同步。
Semaphore类限制可同时访问某一资源或资源池的线程数。线程通过调用 WaitOne方法将信号量减1,并通过调用 Release方法把信号量加Semaphore类中重要方法有:
Semaphore(int initialCount, int maximumCount)该方法是信号量的构造函数initialCount信号量初始化时默认能够允许执行的线程数量,maximumCount表示该信号量能够允许执行线程的最大数量
semaphore.WaitOne();当线程调用的方法中添加了该WaitOne(线程阻塞)的方法时,那么执行的线程数量超过信号量初始化时默认的线程数量时,超过的线程数量就会发生堵塞不在执行,当调用Release方法时传入的参数就是释放掉的信号量(也就是说信号量在初始化的时候默认执行线程的数量的基础上还能够允许执行的线程数量)
semaphore.Release(releaseCount) 当调用Release方法时传入的参数就是释放掉的信号量(也就是说信号量在初始化的时候默认执行线程的数量的基础上还能够允许执行的线程数量)
看下面代码:

// 初始信号量计数为5,最大计数为9  
      //Semaphore(int initialCount, int maximumCount)
       //initialCount表示该信号量默认执行线程数量
       public static Semaphore semaphore = new Semaphore(5,9);
       public static int time = 0;
       static void Main(string[] args)
       {
           for (int i = 0; i < 8; i++)
           {
               Thread test = new Thread(new ParameterizedThreadStart(TestMethod));

               // 开始线程,并传递参数  
               test.Start(i);
           }

           // 等待1秒让所有线程开始并阻塞在信号量上  
           Thread.Sleep(1000);

           // 信号量计数加2   
           // 最后可以看到输出结果次数为7次  
           semaphore.Release(2);
           Console.Read();
       }

       public static void TestMethod(object number)
       {
           // 设置一个时间间隔让输出有顺序  
           int span = Interlocked.Add(ref time, 100);
           Thread.Sleep(1000 + span);

           //信号量计数减1
           //方法放
           semaphore.WaitOne();

           Console.WriteLine("Thread {0} run ", number);
       }

允许结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值