1、實例背景 印表機做兩件事情: 第一件事件負責接受外界列印的請求,包括其他的電腦,把這個列印任務添加到列印隊列當中。 另一件事情就是列印,從列印隊列中取出一個列印任務,完成列印任務,將這個列印任務去掉。 可以肯定的是,這兩件事情是併發進行的,不可能印表機一直去列印,而不去接受新的列印任務,也不可能 ...
1、實例背景
印表機做兩件事情:
第一件事件負責接受外界列印的請求,包括其他的電腦,把這個列印任務添加到列印隊列當中。
另一件事情就是列印,從列印隊列中取出一個列印任務,完成列印任務,將這個列印任務去掉。
可以肯定的是,這兩件事情是併發進行的,不可能印表機一直去列印,而不去接受新的列印任務,也不可能一直接受請求,而不去列印,等到讓油墨幹了紙張爛掉了
如果我們用程式讓印表機幹活的話,顯然我們可以用兩個線程同時做這兩件事情,當然還要考慮其他的事情,就是前面說的併發問題,因為存在著併發競爭資源--印表機隊列。
我們希望的理想情況是最好兩個線程能交替執行,即接受一次列印請求的操作,再執行一次列印的操作,而不是接受請求N次後才執行一次列印任務,所以說我們還需要解決線程之間配合工作的問題,也就是線程協作的問題。
關於線程協作,我們考慮三個問題:
(1)如何在當前線程中通知其他的線程的執行
(2)如何阻止當前線程的執行
(3)其他線程執行完畢如何繼續當前線程的執行
答案:(1)Monitor.Pulse()
(2)Monitor.Wait()
(3)Monitor.Wait()
2、沒有線程協作的印表機工作
class MonitorTest { int MAX = 10;//最多允許10個列印作業 Queue<int> queue; //表示對象的先進先出的集合 public MonitorTest() { queue = new Queue<int>(); } //生產者線程調用的方法:模擬添加列印作業 public void ProducerThread() { Random r = new Random(); lock (queue) { for (int counter = 0; counter < MAX; counter++) { int value = r.Next(100); queue.Enqueue(value); //隨機數入隊列 Console.WriteLine("生產:" + value); } } } //消費者線程 public void ConsumerThread() { lock (queue) { for (int counter = 0; counter < MAX; counter++) { int value = (int)queue.Dequeue(); //第一個元素出隊列 Console.WriteLine("消費:" + value); } } } static void Main(string[] args) { MonitorTest monitor = new MonitorTest(); Thread producer = new Thread(new ThreadStart(monitor.ProducerThread)); Thread consumer = new Thread(new ThreadStart(monitor.ConsumerThread)); producer.Start(); consumer.Start(); Console.WriteLine("印表機工作完畢"); Console.ReadLine(); } }
發現:先把所有的生產任務添加進來,然後再執行消費作業。這是不符合我們的要求的。我們要求是添加一個列印任務就執行一次消費作業
3、有線程協作的印表機工作
class MonitorTest { int MAX = 10;//最多允許10個列印作業 Queue<int> queue; //表示對象的先進先出的集合 public MonitorTest() { queue = new Queue<int>(); } //生產者線程調用的方法:模擬添加列印作業 public void ProducerThread() { Random r = new Random(); lock (queue) { for (int counter = 0; counter < MAX; counter++) { int value = r.Next(100); queue.Enqueue(value); //隨機數入隊列 Console.WriteLine("生產:" + value); //producer線程通知consumer線程從阻塞隊列進入準備隊列 Monitor.Pulse(queue); //釋放等待線程 //producer線程進入阻塞隊列,並放棄了鎖定,使consumer線程得以執行 Monitor.Wait(queue); //等待CosumerThread()完成 } } } //消費者線程 public void ConsumerThread() { lock (queue) { do { if (queue.Count>0) { int value = (int)queue.Dequeue(); //第一個元素出隊列 Console.WriteLine("消費:" + value); Monitor.Pulse(queue);//釋放 } } while (Monitor.Wait(queue)); // 等待ProducerThread()放入數據 } } static void Main(string[] args) { MonitorTest monitor = new MonitorTest(); Thread producer = new Thread(new ThreadStart(monitor.ProducerThread)); Thread consumer = new Thread(new ThreadStart(monitor.ConsumerThread)); producer.Start(); consumer.Start(); Console.WriteLine("印表機工作完畢"); Console.ReadLine(); } }
解釋:
當producer線程Start啟動後,進入運行狀態後,產生了一個隨機數,並添加到queue隊列容器里,然後碰到代碼 Monitor.Pulse(queue) ; 那麼producer線程就通知線程consumer從阻塞隊列進入準備隊列。然後producer線程又碰到Monitor.Wait(queue); producer線程進入等待狀態(即阻塞了自己),他放棄了對queue的鎖定,所以線程consumer得以執行。
那麼線程consumer執行了,當執行到do**while迴圈處,由於第一次執行,所以不判斷條件,直接從queue隊列里“請出”第一個元素,然後他碰到代碼Monitor.Pulse(queue) ; ,那麼線程consumer就通知producer線程從阻塞隊列進入準備隊列,然後他判斷while(Monitor.Wait(queue); ),因為這是第一次執行,所以
結果顯示是 生產者生產一個,消費者接著就消費一個。如此迴圈中。。。