【转】细说.NET中的多线程 (五 使用信号量进行同步)
上一節主要介紹了使用鎖進行同步,本節主要介紹使用信號量進行同步
使用EventWaitHandle信號量進行同步
EventWaitHandle主要用于實現信號燈機制。信號燈主要用于通知等待的線程。主要有兩種實現:AutoResetEvent和ManualResetEvent。
AutoResetEvent
AutoResetEvent從字面上理解是一個自動重置的事件。舉個例子,假設有很多人等在門外,AutoResetEvent更像一個十字旋轉門,每一次只允許一個人進入,進入之后門仍然是關閉狀態。
下面的例子演示了使用方式:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using?System; using?System.Threading; class?BasicWaitHandle { ????static?EventWaitHandle _waitHandle =?new?AutoResetEvent(false); ? ????static?void?Main() ????{ ????????for?(int?i = 0; i < 3; i++) ????????????new?Thread(Waiter).Start(); ? ????????for?(int?i = 0; i < 3; i++) ????????{ ????????????Thread.Sleep(1000);??????????????????// Pause for a second... ????????????Console.WriteLine("通知下一個線程進入"); ????????????_waitHandle.Set();????????????????????// Wake up the Waiter. ????????} ????????Console.ReadLine(); ????} ? ????static?void?Waiter() ????{ ????????var?threadId = Thread.CurrentThread.ManagedThreadId; ????????Console.WriteLine("線程 {0} 正在等待", threadId); ????????_waitHandle.WaitOne();????????????????// 等待通知 ????????Console.WriteLine("線程 {0} 得到通知,可以進入", threadId); ????} } |
雙向信號燈
某些情況下,如果你連續的多次使用Set方法通知工作線程,這個時候工作線程可能還沒有準備好接收信號,這樣的話后面的幾次Set通知可能會沒有效果。這種情況下,你需要讓主線程得到工作線程接收信息的通知再開始發送信息。你可能需要通過兩個信號燈實現這個功能。
示例代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using?System; using?System.Threading; class?TwoWaySignaling { ????static?EventWaitHandle _ready =?new?AutoResetEvent(false); ????static?EventWaitHandle _go =?new?AutoResetEvent(false); ????static?readonly?object?_locker =?new?object(); ????static?string?_message; ? ????static?void?Main() ????{ ????????new?Thread(Work).Start(); ? ????????_ready.WaitOne();??????????????????// 在工作線程準備接收信息之前需要一直等待 ????????lock?(_locker) _message =?"床前明月光"; ????????_go.Set();?????????????????????????// 通知工作線程開始工作 ? ????????_ready.WaitOne(); ????????lock?(_locker) _message =?"疑是地上霜"; ????????_go.Set(); ????????_ready.WaitOne(); ????????lock?(_locker) _message =?"結束";????// 告訴工作線程退出 ????????_go.Set(); ? ????????Console.ReadLine(); ????} ? ????static?void?Work() ????{ ????????while?(true) ????????{ ????????????_ready.Set();??????????????????????????// 表示當前線程已經準備接收信號 ????????????_go.WaitOne();?????????????????????????// 工作線程等待通知 ????????????lock?(_locker) ????????????{ ????????????????if?(_message ==?"結束")?return;????????// 優雅的退出~-~ ????????????????Console.WriteLine(_message); ????????????} ????????} ????} } |
生產消費隊列
生產消費隊列是多線程編程里常見的的需求,他的主要思路是:
示例代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | using?System; using?System.Threading; using?System.Collections.Generic; ?? class?ProducerConsumerQueue : IDisposable { ??EventWaitHandle _wh =?new?AutoResetEvent (false); ??Thread _worker; ??readonly?object?_locker =?new?object(); ??Queue<string> _tasks =?new?Queue<string>(); ?? ??public?ProducerConsumerQueue() ??{ ????_worker =?new?Thread (Work); ????_worker.Start(); ??} ?? ??public?void?EnqueueTask (string?task) ??{ ????lock?(_locker) _tasks.Enqueue (task); ????_wh.Set(); ??} ?? ??public?void?Dispose() ??{ ????EnqueueTask (null);?????// Signal the consumer to exit. ????_worker.Join();?????????// Wait for the consumer's thread to finish. ????_wh.Close();????????????// Release any OS resources. ??} ?? ??void?Work() ??{ ????while?(true) ????{ ??????string?task =?null; ??????lock?(_locker) ????????if?(_tasks.Count > 0) ????????{ ??????????task = _tasks.Dequeue(); ??????????if?(task ==?null)?return; ????????} ??????if?(task !=?null) ??????{ ????????Console.WriteLine ("Performing task: "?+ task); ????????Thread.Sleep (1000);??// simulate work... ??????} ??????else ????????_wh.WaitOne();?????????// No more tasks - wait for a signal ????} ??} } |
為了保證線程安全,我們使用lock來保護Queue<string>集合。我們也顯示的關閉了WaitHandle。
在.NET 4.0中,一個新的類BlockingCollection實現了類似生產者消費者隊列的功能。
ManualResetEvent
ManualResetEvent從字面上看是一個需要手動關閉的事件。舉個例子:假設有很多人等在門外,它像是一個普通的門,門開啟之后,所有等在門外的人都可以進來,當你關閉門之后,不再允許外面的人進來。
示例代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | using?System; using?System.Threading; class?BasicWaitHandle { ????static?EventWaitHandle _waitHandle =?new?ManualResetEvent(false); ? ????static?void?Main() ????{ ????????for?(int?i = 0; i < 3; i++) ????????????new?Thread(Waiter).Start(); ? ? ????????Thread.Sleep(1000);??????????????????// Pause for a second... ????????Console.WriteLine("門已打開,線程進入"); ????????_waitHandle.Set();????????????????????// Wake up the Waiter. ? ????????new?Thread(Waiter).Start(); ? ????????Thread.Sleep(2000); ? ????????_waitHandle.Reset(); ????????Console.WriteLine("門已關閉,線程阻塞"); ? ????????new?Thread(Waiter).Start(); ? ????????Console.ReadLine(); ????} ? ????static?void?Waiter() ????{ ????????var?threadId = Thread.CurrentThread.ManagedThreadId; ????????Console.WriteLine("線程 {0} 正在等待", threadId); ????????_waitHandle.WaitOne();????????????????// 等待通知 ????????Console.WriteLine("線程 {0} 得到通知,可以進入", threadId); ????} } |
ManualResetEvent可以在當前線程喚醒所有等待的線程,這一應用非常重要。
CountdownEvent
CountdownEvent的使用和ManualEvent正好相反,是多個線程共同喚醒一個線程。
示例代碼:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using?System; using?System.Threading; class?CountDownTest { ????static?CountdownEvent _countdown =?new?CountdownEvent(3); ? ????static?void?Main() ????{ ????????new?Thread(SaySomething).Start("I am thread 1"); ????????new?Thread(SaySomething).Start("I am thread 2"); ????????new?Thread(SaySomething).Start("I am thread 3"); ? ????????_countdown.Wait();???// 當前線程被阻塞,直到收到 _countdown發送的三次信號 ????????Console.WriteLine("All threads have finished speaking!"); ? ????????Console.ReadLine(); ????} ? ????static?void?SaySomething(object?thing) ????{ ????????Thread.Sleep(1000); ????????Console.WriteLine(thing); ????????_countdown.Signal(); ????} } |
創建跨進程的EventWaitHandle
EventWaitHandle的構造方法允許創建一個命名的EventWaitHandle,來實現跨進程的信號量操作。名字只是一個簡單的字符串,只要保證不會跟其它進程的鎖沖突即可。
示例代碼:
| 1 | EventWaitHandle wh =?new?EventWaitHandle(false, EventResetMode.AutoReset,?"MyCompany.MyApp.SomeName"); |
如果兩個進程運行這段代碼,信號量會作用于兩個進程內所有的線程。
總結
以上是生活随笔為你收集整理的【转】细说.NET中的多线程 (五 使用信号量进行同步)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 春节之前如何提信用卡额度
- 下一篇: 【转】细说.NET 中的多线程 (一 概