白話併發衝突與線程同步(3)——Mutex、EventWaitHandle、AutoResetEvent 和 Manua (2012-05-18 13:16:12)

     不過這熱氣是從實在的火裏發出來的呢,還是從他的愛情裏發出來的呢,他完全不知道。他的一切光彩現在都沒有了。這是因爲他在旅途中失去了呢,還是悲愁的結果,誰也說不出來。
                                                                                   
 ——安徒生
                                                                                          摘自《堅定的錫兵》

摘要

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才肯辦事,所以運行起來的效果和前一個程序一樣。

class Program
{
   
 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()函數 

看下這個小程序

1 class Program
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 的子類,功能都差不多,就不多說了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章