——安徒生
摘自《堅定的錫兵》
摘要
1-2-3 翻開那《葵花寶典》,只見頁首赫然寫着幾個大字:“欲練神功,必先自宮”,旁邊幾行歪歪扭扭的小字,又不知是哪位前輩高人所寫:“在WC裏佔蹲位的3種方法:1. 如果你只對某個蹲位情有獨鍾,就要WaitOne(),但是不要忘了ReleaseMutex(),千萬別WaitOne()兩次只 ReleaseMutex()一次(你幹這種佔着MK不LS的事,憋壞了後來的小朋友怎麼辦?就算沒有小朋友,憋壞了小貓小狗也不好啊……);2. 如果你喜歡講排場,需要佔2個蹲位才肯辦事,則要WaitAll([蹲位1, 蹲位2]);3. 如果你覺得隨便去哪個蹲位辦事都無所謂,那就可以WaitAny([蹲位1, 蹲位2])……”。
Mutex的WaitOne()函數
前幾天1-2-3去黑木崖找東方不敗玩,聽到東方不敗抱怨說整天繡花眼睛好累呀,於是1-2-3就給東方不敗編了一個活動眼睛的程序。
class Program
{ static void Main(string[] args) { // 爲截圖方便把窗體設小一點 Console.WindowWidth = 30; Console.BufferWidth = 30; Console.WindowHeight = 16; Console.BufferHeight = 16; Mutex mk = new Mutex(false, "my mutex"); for (int i = 0; i < 1000; i++) { mk.WaitOne(); for (int j = 0; j < 30; j++) { Console.Write(">"); Thread.Sleep(100); } mk.ReleaseMutex(); Thread.Sleep(500); } } } |
接連運行此程序的兩個實例,把它們並排排放在一起(如下圖所示),即可看到箭頭從左邊的窗體“穿越”到右邊窗體的效果了。
是的,我們需要同步兩個進程(中的主線程),這個工作需要交給Mutex。Mutex和Monitor的概念十分相似,只不過Monitor是.net內建的線程同步機制,Mutex是封裝了Windows操作系統的線程同步機制;Monitor速度快,Mutex的速度要比Monitor慢很多;Monitor只能用於同步同一進程內的線程;Mutex則可以用於同步隸屬於不同進程的線程。
Mutex的WaitAll()函數
現在我們對WC進行了擴建,把mk增加到兩個,可是卻遇到了兩個講排場的進程,它們都要同時佔兩個mk才肯辦事,所以運行起來的效果和前一個程序一樣。
{
static void Main(string[] args)
{
// 爲截圖方便把窗體設小一點
Console.WindowWidth = 30; Console.BufferWidth = 30;
Console.WindowHeight = 16; Console.BufferHeight = 16;
Mutex mk1 = new Mutex(false, "my mutex1");
Mutex mk2 = new Mutex(false, "my mutex2");
Mutex[] mks = new Mutex[] { mk1, mk2 };
for (int i = 0; i < 1000; i++)
{
Mutex.WaitAll(mks);
for (int j = 0; j < 30; j++)
{
Console.Write(">");
Thread.Sleep(100);
}
mk1.ReleaseMutex();
mk2.ReleaseMutex();
Thread.Sleep(500);
}
}
}
Mutex的WaitAny()函數
看下這個小程序
2 {
3 static void Main(string[] args)
4 {
5 // 爲截圖方便把窗體設小一點
6 Console.WindowWidth = 30; Console.BufferWidth = 30;
7 Console.WindowHeight = 16; Console.BufferHeight = 16;
8
9 Mutex mk1 = new Mutex(false, "my mutex1");
10 Mutex mk2 = new Mutex(false, "my mutex2");
11 Mutex[] mks = new Mutex[] { mk1, mk2 };
12
13 for (int i = 0; i < 1000; i++)
14 {
15 int index = Mutex.WaitAny(mks); // 返回值爲此進程佔用的mk在mks裏的index
16 Console.Write("Index: " + index.ToString());
17 for (int j = 0; j < 30; j++)
18 {
19 Console.Write(">");
20 Thread.Sleep(100);
21 }
22
23 mks[index].ReleaseMutex();
24 Thread.Sleep(new Random().Next(100, 3000));
25 }
26 }
27 }
如果同時運行此程序的兩個實例,正如本文摘要裏所寫的,只要mk1和mk2有一個是空閒的,進程就可以進去辦事,所以兩個進程可以同時輸出">"字符。注意程序的第24行,每個進程在輸出30個">"字符後都會隨機Sleep 100到3000毫秒,這樣就有可能出現mk1和mk2同時空閒的情況,所以就會出現一會兒進程1佔用mk1而進程2佔用mk2;一會兒進程1佔用mk2 而進程2佔用mk1的情況(在下圖分別用綠色和紅色波浪線標出)。
EventWaitHandle、AutoResetEvent 和 ManualResetEvent
EventWaitHandle的名字與Mutex差了很多,不過它可是Mutex不折不扣的兄弟——它和Mutex都是WaitHandle的子類,用法也差不多。下面這兩段程序實現了與本文的第一段程序相同的功能。
class Program
{
static void Main(string[] args)
{
// 爲截圖方便把窗體設小一點
Console.WindowWidth = 30; Console.BufferWidth = 30;
Console.WindowHeight = 16; Console.BufferHeight = 16;
EventWaitHandle mk = new EventWaitHandle(true, EventResetMode.AutoReset, "my mk");
for (int i = 0; i < 1000; i++)
{
mk.WaitOne();
for (int j = 0; j < 30; j++)
{
Console.Write(">");
Thread.Sleep(100);
}
mk.Set();
Thread.Sleep(500);
}
}
}
class Program
{
static void Main(string[] args)
{
// 爲截圖方便把窗體設小一點
Console.WindowWidth = 30; Console.BufferWidth = 30;
Console.WindowHeight = 16; Console.BufferHeight = 16;
EventWaitHandle mk = new EventWaitHandle(true, EventResetMode.ManualReset, "my mk");
for (int i = 0; i < 1000; i++)
{
mk.WaitOne();
mk.Reset(); // 把這行去掉會咋樣?
for (int j = 0; j < 30; j++)
{
Console.Write(">");
Thread.Sleep(100);
}
mk.Set();
Thread.Sleep(500);
}
}
}
AutoResetEvent 和 ManualResetEvent 是 EventWaitHandle 的子類,功能都差不多,就不多說了。