C# 中使用 lock 和 Monitor 控制多线程对资源的使用, 最常见的生产者和消费者问题就是多线程同步和通信的经典例子. 了解 C# 多线程的同步与通信.
一, 关于 lock 和 Monitor
lock 可以把一段代码定义为互斥段 (critical section), 互斥段在一个时刻内只允许一个线程进入执行, 而其它线程必须等待. 格式定义如下:
lock(expression) statement_block
expression 代表要跟踪的对象, 通常是引用. 一般地, 如果想保护一个类的实例, 使用 this; 如果保护一个静态变量 (如互斥代码段在一个静态方法内部), 使用类名就可以了. 而 statement_block 就是互斥段的代码.
Monitor 用于多线程公用一个对象时使线程共享资源的方案. Monitor 必须和一个具体的对象相关联.
二, 生产者和消费者问题
假设两个线程同时维护一个队列, 如果一个线程对队列中更新元素, 而另外一个线程从队列中获取元素, 那么我们称更新元素的线程为生产者, 称获取元素的线程为消费者.
1, 被操作对象
- /// <summary>;
- /// 被操作对象
- /// </summary>;
- public class Counter
- {
- // 更新和读取的数字
- private int numberOfCounter;
- // 读操作可执行标记, 可以防止死锁的发生
- private bool readFlag = false;
- public void Read()
- {
- // 锁定后, 其它读操作等待这一次读操作完成
- lock (this)
- {
- // 第一次之行为 flase, 进入等待
- if (!readFlag)
- {
- try
- {
- // 进入等待读, 另一个线程写
- Monitor.Wait(this);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- }
- Console.WriteLine("消费 (获取): {0}", numberOfCounter);
- // 重置, 消费已经完成
- readFlag = false;
- Monitor.Pulse(this);
- }
- }
- public void Write(int number)
- {
- // 锁定后, 其它写操作等待这一次写操作完成
- lock (this)
- {
- // 第一次 readFlag 为 flase, 跳过执行下边的写
- // 如果当前正在读, 等待读操作执行 Monitor.Pulse
- if (readFlag)
- {
- try
- {
- Monitor.Wait(this);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- }
- numberOfCounter = number;
- Console.WriteLine("生产 (更新): {0}", numberOfCounter);
- // 重置, 生产已经完成
- readFlag = true;
- // 同步通过等待 Pulse 来完成
- Monitor.Pulse(this);
- }
- }
- }
2, 生产者和消费者
- /// <summary>;
- /// 生产者
- /// </summary>
- public class CounterWrite
- {
- Counter counter;
- // 生产者生产次数
- int quantity = 1;
- public CounterWrite(Counter box, int request)
- {
- // 构造函数
- counter = box;
- quantity = request;
- }
- // 生产者向操作对象更新信息
- public void Write()
- {
- for (int i = 1; i <= quantity; i++)
- counter.Write(i);
- }
- }
- /// <summary>
- /// 消费者
- /// </summary>
- public class CounterRead
- {
- Counter counter;
- // 生产者生产次数
- int quantity = 1;
- public CounterRead(Counter box, int request)
- {
- // 构造函数
- counter = box;
- quantity = request;
- }
- // 消费者从操作对象中获取信息
- public void Read()
- {
- for (int i = 1; i <= quantity; i++)
- counter.Read();
- }
- }
3, 线程操作
- Counter counter = new Counter();
- CounterRead read = new CounterRead(counter, 10);
- CounterWrite write = new CounterWrite(counter, 10);
- Thread th1 = new Thread(new ThreadStart(read.Read));
- Thread th2 = new Thread(new ThreadStart(write.Write));
- th1.Start();
- th2.Start();
- th1.Join();
- th2.Join();
- Console.ReadLine();
通过 lock 锁定 Counter 对象的引用, 初始 readFlag 为 false 控制线程 1 等待读取: Monitor.Wait(this),
线程 2 写入, 然后更改 readFlag, 然后执行: Monitor.Pulse(this), 通知等待队列中的线程请求对象状态已发生改变,
线程 1 锁定 this, 执行读操作, 然后更改 readFlag, 线程 1 和线程 2 交互执行写读的操作.
同时因为 readFlag 的存在和交替更新, 避免了死锁情况的发生.
来源: http://www.bubuko.com/infodetail-3104167.html