C#中使用lock和Monitor控制多线程对资源的使用,最常见的生产者和消费者问题就是多线程同步和通信的经典例子。这篇文章通过例子来了解C#多线程的同步与通信。

一、关于lock和Monitor

lock可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其它线程必须等待。格式定义如下:

 
  
  1. 查看源代码打印?lock(expression) statement_block   

expression代表要跟踪的对象,通常是引用。一般地,如果想保护一个类的实例,使用this;如果保护一个静态变量(如互斥代码段在一个静态方法内部),使用类名就可以了。而statement_block就是互斥段的代码。

Monitor用于多线程公用一个对象时使线程共享资源的方案。Monitor必须和一个具体的对象相关联。

二、生产者和消费者问题

假设两个线程同时维护一个队列,如果一个线程对队列中更新元素,而另外一个线程从队列中获取元素,那么我们称更新元素的线程为生产者,称获取元素的线程为消费者。

1、被操作对象

 
  
  1. /// <summary>    
  2.  
  3.    /// 被操作对象    
  4.  
  5.    /// </summary>    
  6.  
  7.    public class Counter    
  8.  
  9.    {    
  10.  
  11.        //更新和读取的数字    
  12.  
  13.        private int numberOfCounter;    
  14.  
  15.        //读操作可执行标记,可以防止死锁的发生    
  16.  
  17.        private bool readFlag = false;    
  18.  
  19.     
  20.  
  21.        public void Read()    
  22.  
  23.        {    
  24.  
  25.            //锁定后,其它读操作等待这一次读操作完成    
  26.  
  27.            lock (this)    
  28.  
  29.            {    
  30.  
  31.                //第一次之行为flase,进入等待    
  32.  
  33.                if (!readFlag)    
  34.  
  35.                {    
  36.  
  37.                    try   
  38.  
  39.                    {    
  40.  
  41.                        //进入等待读,另一个线程写    
  42.  
  43.                        Monitor.Wait(this);    
  44.  
  45.                    }    
  46.  
  47.                    catch (Exception ex)    
  48.  
  49.                    {    
  50.  
  51.                        Console.WriteLine(ex);    
  52.  
  53.                    }    
  54.  
  55.                }    
  56.  
  57.     
  58.  
  59.                Console.WriteLine("消费(获取): {0}", numberOfCounter);    
  60.  
  61.     
  62.  
  63.                //重置,消费已经完成    
  64.  
  65.                readFlag = false;    
  66.  
  67.                Monitor.Pulse(this);    
  68.  
  69.            }    
  70.  
  71.        }    
  72.  
  73.     
  74.  
  75.        public void Write(int number)    
  76.  
  77.        {    
  78.  
  79.            //锁定后,其它写操作等待这一次写操作完成    
  80.  
  81.            lock (this)    
  82.  
  83.            {    
  84.  
  85.                //第一次readFlag为flase,跳过执行下边的写    
  86.  
  87.                //如果当前正在读,等待读操作执行Monitor.Pulse    
  88.  
  89.                if (readFlag)    
  90.  
  91.                {    
  92.  
  93.                    try   
  94.  
  95.                    {    
  96.  
  97.                        Monitor.Wait(this);    
  98.  
  99.                    }    
  100.  
  101.                    catch (Exception ex)    
  102.  
  103.                    {    
  104.  
  105.                        Console.WriteLine(ex);    
  106.  
  107.                    }    
  108.  
  109.                }    
  110.  
  111.                numberOfCounter = number;    
  112.  
  113.                Console.WriteLine("生产(更新): {0}", numberOfCounter);    
  114.  
  115.     
  116.  
  117.                //重置,生产已经完成    
  118.  
  119.                readFlag = true;    
  120.  
  121.     
  122.  
  123.                //同步通过等待Pulse来完成    
  124.  
  125.                Monitor.Pulse(this);    
  126.  
  127.            }    
  128.  
  129.        }    
  130.  
  131.    }   

 

2、生产者和消费者

 
  
  1. /// <summary>    
  2.  
  3.     /// 生产者    
  4.  
  5.     /// </summary>    
  6.  
  7.     public class CounterWrite    
  8.  
  9.     {    
  10.  
  11.         Counter counter;    
  12.  
  13.         //生产者生产次数    
  14.  
  15.         int quantity = 1;    
  16.  
  17.      
  18.  
  19.         public CounterWrite(Counter box, int request)    
  20.  
  21.         {    
  22.  
  23.             //构造函数    
  24.  
  25.             counter = box;    
  26.  
  27.             quantity = request;    
  28.  
  29.         }    
  30.  
  31.      
  32.  
  33.         //生产者向操作对象更新信息    
  34.  
  35.         public void Write()    
  36.  
  37.         {    
  38.  
  39.             for (int i = 1; i <= quantity; i++)    
  40.  
  41.                 counter.Write(i);    
  42.  
  43.         }    
  44.  
  45.     }    
  46.  
  47.      
  48.  
  49.     /// <summary>    
  50.  
  51.     /// 消费者    
  52.  
  53.     /// </summary>    
  54.  
  55.     public class CounterRead    
  56.  
  57.     {    
  58.  
  59.         Counter counter;    
  60.  
  61.         //生产者生产次数    
  62.  
  63.         int quantity = 1;    
  64.  
  65.      
  66.  
  67.         public CounterRead(Counter box, int request)    
  68.  
  69.         {    
  70.  
  71.             //构造函数    
  72.  
  73.             counter = box;    
  74.  
  75.             quantity = request;    
  76.  
  77.         }    
  78.  
  79.      
  80.  
  81.         //消费者从操作对象中获取信息    
  82.  
  83.         public void Read()    
  84.  
  85.         {    
  86.  
  87.             for (int i = 1; i <= quantity; i++)    
  88.  
  89.                 counter.Read();    
  90.  
  91.         }    
  92.  
  93.     }   

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的存在和交替更新,避免了死锁情况的发生。