之前在博客園看到有位仁兄發(fā)表一篇關(guān)于AutoResetEvent介紹,,看了下他寫(xiě)的代碼,看上去沒(méi)什么問(wèn)題,,但仔細(xì)看還是能發(fā)現(xiàn)問(wèn)題,。下圖是這位仁兄代碼截圖。
仁兄博客地址:http://www.cnblogs.com/lzjsky/archive/2011/07/11/2102794.html
按照這種寫(xiě)法自己試了下,,運(yùn)行起來(lái)并不是他這種結(jié)果(運(yùn)行結(jié)果很隨機(jī)),。
原因有以下兩點(diǎn):
1、支付線程與取書(shū)線程都屬于同級(jí)線程,運(yùn)行先后順序是隨機(jī)的
2,、在循環(huán)內(nèi)部調(diào)用AutoResetEvent.Set(),,不能確定子線程是否按順序執(zhí)行,有可能主線程已經(jīng)循環(huán)多次,,而子線程可能才循環(huán)一次
修正
首先,,要明白實(shí)驗(yàn)的場(chǎng)景。還是引用這位仁兄的例子:“我去書(shū)店買(mǎi)書(shū),,當(dāng)我選中一本書(shū)后我會(huì)去收費(fèi)處付錢(qián),,付好錢(qián)后再去倉(cāng)庫(kù)取書(shū)。這個(gè)順序不能顛倒,,我作為主線程,,收費(fèi)處和倉(cāng)庫(kù)做兩個(gè)輔助線程” 。
要實(shí)現(xiàn)上圖這種效果,,得先確定好執(zhí)行先后順序(上面已經(jīng)說(shuō)過(guò)):挑書(shū)-->收費(fèi)-->取書(shū)-->完成,。
代碼編寫(xiě)如下:
1 class Program 2 { 3 static int _num = 0; 4 //本例重點(diǎn)對(duì)象 5 static AutoResetEvent _autoReset = new AutoResetEvent(false); 6 7 static AutoResetEvent _autoReset0 = new AutoResetEvent(false); 8 static AutoResetEvent _autoReset1 = new AutoResetEvent(false); 9 10 //static AutoResetEvent autoReset2 = new AutoResetEvent(false); 11 //static AutoResetEvent autoReset3 = new AutoResetEvent(false); 12 13 //static object _payMoneyObj = new object(); 14 //static object _getBookObj = new object(); 15 16 private static void ThreadPayMoneyProc() 17 { 18 while (true) 19 { 20 //_autoReset.WaitOne(); 21 _autoReset0.WaitOne(); 22 //lock (_payMoneyObj) 23 { 24 Console.WriteLine(Thread.CurrentThread.Name + ",編號(hào): " + _num); 25 //通知主線程,錢(qián)已付完 26 //_autoReset2.Set(); 27 } 28 } 29 } 30 31 private static void TreadGetBookProc() 32 { 33 while (true) 34 { 35 //_autoReset.WaitOne(); 36 _autoReset1.WaitOne(); 37 //lock (_getBookObj) 38 { 39 Console.WriteLine(Thread.CurrentThread.Name + ",編號(hào): " + _num); 40 //通知主線程,,書(shū)已取走 41 //_autoReset3.Set(); 42 } 43 } 44 } 45 46 47 static void Main(string[] args) 48 { 49 //本案例是通過(guò)AutoResetEvent來(lái)實(shí)現(xiàn)多線程同步 50 //購(gòu)買(mǎi)書(shū)數(shù)量 51 const int num = 50; 52 53 //付錢(qián)線程 54 Thread threadPayMoney = new Thread(new ThreadStart(ThreadPayMoneyProc)); 55 threadPayMoney.Name = "付錢(qián)線程"; 56 //取書(shū)線程 57 Thread threadGetBook = new Thread(new ThreadStart(TreadGetBookProc)); 58 threadGetBook.Name = "取書(shū)線程"; 59 60 //開(kāi)始執(zhí)行線程 61 threadPayMoney.Start(); 62 threadGetBook.Start(); 63 64 //主線程開(kāi)始選書(shū) 65 Console.WriteLine("----------------主線程開(kāi)始選書(shū),!------------------"); 66 for (int i = 1; i <= num; i++) 67 { 68 Console.WriteLine("主線程選書(shū)編號(hào):" + i); 69 _num = i; 70 //_autoReset.Set(); 71 72 //通知付錢(qián)線程 73 _autoReset0.Set(); 74 //主線延時(shí)1ms執(zhí)行(但不知道付錢(qián)線程這個(gè)過(guò)程需要多少時(shí)間) 75 Thread.Sleep(1); 76 //_autoReset2.WaitOne(); 77 78 //付完錢(qián)后,通知取書(shū)線程 79 _autoReset1.Set(); 80 //主線延時(shí)1ms執(zhí)行(但不知道取書(shū)線程這個(gè)過(guò)程需要多少時(shí)間) 81 Thread.Sleep(1); 82 //_autoReset3.WaitOne(); 83 Console.WriteLine("-----------------------------------"); 84 } 85 86 Console.ReadKey(); 87 88 89 } 90 }
運(yùn)行結(jié)果如下圖:
這樣做,,效果是出來(lái)了,,但主線程不知道付費(fèi)線程、取書(shū)線程執(zhí)行需要多長(zhǎng)時(shí)間,。上例中給定的是1ms,,但如果其中某個(gè)子線程超過(guò)了給定的休眠時(shí)間,主線會(huì)繼續(xù)往下執(zhí)行,,不會(huì)等待子線程處理完成,。這樣就導(dǎo)致了買(mǎi)書(shū)編號(hào)與付錢(qián)和取書(shū)的編號(hào)不同步。也就混亂了,。
這時(shí)可以使用AutoResetEvent這個(gè)對(duì)象,。上例中已經(jīng)使用這個(gè)對(duì)象。沒(méi)錯(cuò),,還可以在繼續(xù)使用,。
代碼如下圖:
1 class Program 2 { 3 static int _num = 0; 4 //本例重點(diǎn)對(duì)象 5 static AutoResetEvent _autoReset = new AutoResetEvent(false); 6 7 static AutoResetEvent _autoReset0 = new AutoResetEvent(false); 8 static AutoResetEvent _autoReset1 = new AutoResetEvent(false); 9 10 static AutoResetEvent _autoReset2 = new AutoResetEvent(false); 11 static AutoResetEvent _autoReset3 = new AutoResetEvent(false); 12 13 //static object _payMoneyObj = new object(); 14 //static object _getBookObj = new object(); 15 16 private static void ThreadPayMoneyProc() 17 { 18 while (true) 19 { 20 //_autoReset.WaitOne(); 21 _autoReset0.WaitOne(); 22 //lock (_payMoneyObj) 23 { 24 Console.WriteLine(Thread.CurrentThread.Name + ",編號(hào): " + _num); 25 //通知主線程,錢(qián)已付完成 26 _autoReset2.Set(); 27 } 28 } 29 } 30 31 private static void TreadGetBookProc() 32 { 33 while (true) 34 { 35 //_autoReset.WaitOne(); 36 _autoReset1.WaitOne(); 37 //lock (_getBookObj) 38 { 39 Console.WriteLine(Thread.CurrentThread.Name + ",編號(hào): " + _num); 40 //通知主線程,,書(shū)已取走 41 _autoReset3.Set(); 42 } 43 } 44 } 45 46 47 static void Main(string[] args) 48 { 49 //本案例是通過(guò)AutoResetEvent來(lái)實(shí)現(xiàn)多線程同步 50 //購(gòu)買(mǎi)書(shū)數(shù)量 51 const int num = 5; 52 53 //付錢(qián)線程 54 Thread threadPayMoney = new Thread(new ThreadStart(ThreadPayMoneyProc)); 55 threadPayMoney.Name = "付錢(qián)線程"; 56 //取書(shū)線程 57 Thread threadGetBook = new Thread(new ThreadStart(TreadGetBookProc)); 58 threadGetBook.Name = "取書(shū)線程"; 59 60 //開(kāi)始執(zhí)行線程 61 threadPayMoney.Start(); 62 threadGetBook.Start(); 63 64 //主線程開(kāi)始選書(shū) 65 Console.WriteLine("----------------主線程開(kāi)始選書(shū),!------------------"); 66 for (int i = 1; i <= num; i++) 67 { 68 Console.WriteLine("主線程選書(shū)編號(hào):" + i); 69 _num = i; 70 //_autoReset.Set(); 71 72 //通知付錢(qián)線程 73 _autoReset0.Set(); 74 //主線延時(shí)1ms執(zhí)行(但不知道付錢(qián)線程這個(gè)過(guò)程需要多少時(shí)間) 75 //Thread.Sleep(1); 76 //等待付錢(qián)線程 77 _autoReset2.WaitOne(); 78 79 //付完錢(qián)后,通知取書(shū)線程 80 _autoReset1.Set(); 81 //主線延時(shí)1ms執(zhí)行(但不知道取書(shū)線程這個(gè)過(guò)程需要多少時(shí)間) 82 //Thread.Sleep(1); 83 //等待取書(shū)線程 84 _autoReset3.WaitOne(); 85 Console.WriteLine("-----------------------------------"); 86 //完成后,,繼續(xù)下一個(gè)任務(wù)處理 87 } 88 89 Console.ReadKey(); 90 91 92 } 93 }
運(yùn)行結(jié)果如下圖:
運(yùn)行結(jié)果和上面使用指定主線程休眠所運(yùn)行結(jié)果是一樣的,。但是,可以不用指定主線程休眠時(shí)間,也不需要指定,。因?yàn)槟銢](méi)法估計(jì)子線程所運(yùn)行的時(shí)間,,而且每次運(yùn)行時(shí)間都不一樣。
后話
本例中,, 買(mǎi)書(shū)場(chǎng)景其實(shí)有兩種編程結(jié)構(gòu)(或者編程思想),。一種是本例中的,買(mǎi)書(shū)是主線程,,而收銀臺(tái)(付錢(qián)線程),、倉(cāng)庫(kù)(取書(shū)線程)。這兩個(gè)線程是一直存在的,,一直跑著的,。只要有書(shū)過(guò)來(lái),這兩個(gè)線程就會(huì)執(zhí)行,。這可以聯(lián)系到現(xiàn)實(shí)中的收銀臺(tái)和倉(cāng)庫(kù),。
第二種編程思想,買(mǎi)書(shū)是一個(gè)發(fā)起線程,,然后開(kāi)啟一個(gè)付款線程和取書(shū)線程,。這時(shí),買(mǎi)書(shū)線程(主線程)可以確定這兩個(gè)子線程什么時(shí)候執(zhí)行完成,。使用 線程對(duì)象.Join(),,執(zhí)行完后,主線程接著下步任務(wù)處理,。