日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

多线程的那群“象”

發(fā)布時(shí)間:2023/12/10 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多线程的那群“象” 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  最初學(xué)習(xí)多線程的時(shí)候,只學(xué)了用Thread這個(gè)類(lèi),記憶中也用過(guò)Mutex,到后來(lái)只記得Thread的使用,其余的都忘了。知道前不久寫(xiě)那個(gè)Socket連接池時(shí)遇到了一些對(duì)象如:Semaphore,Interlocked,Mutex等,才知道多線程中有這么多好東西,當(dāng)時(shí)用了一下有初步了解,現(xiàn)在來(lái)熟悉熟悉。

  本文介紹的多線程這個(gè)“象群”包括:Interlocked,Semaphore,Mutex,Monitor,ManualResetEvent,AutoRestEvent。而使用的例子則有車(chē)票競(jìng)搶和類(lèi)似生產(chǎn)者消費(fèi)者的Begin/End(這里的Begin/End跟異步里面的沒(méi)關(guān)系)兩個(gè)事件模型。

先來(lái)看一下本文“象群”的類(lèi)圖

?

Interlocked為多個(gè)線程共享的變量提供原子操作

  在平常多線程中為了保護(hù)某個(gè)互斥的資源在多線程中不會(huì)因?yàn)橘Y源共享而出問(wèn)題,都會(huì)使用lock關(guān)鍵字。如果這個(gè)資源只是一個(gè)單單的計(jì)數(shù)量的話,就可以用這個(gè)Interlocked了,調(diào)用Increment方法可以是遞增,Decrement則是遞減。下面則是MSDN上的說(shuō)明

  此類(lèi)的方法可以防止可能在下列情況發(fā)生的錯(cuò)誤:計(jì)劃程序在某個(gè)線程正在更新可由其他線程訪問(wèn)的變量時(shí)切換上下文;或者當(dāng)兩個(gè)線程在不同的處理器上并發(fā)執(zhí)行時(shí)。?此類(lèi)的成員不引發(fā)異常。

  由于這里就使用車(chē)票競(jìng)搶的例子吧!假設(shè)有10張車(chē)票,有多個(gè)售票點(diǎn)去銷(xiāo)售,賣(mài)光就沒(méi)有了

這個(gè)是線程的方法

1 public void ThreadingCount2() 2 { 3 while (true) 4 { 5 //賣(mài)光就停止銷(xiāo)售了 6 if (count >= 10) 7 break; 8 Interlocked.Increment(ref count); 9 //搶到車(chē)票的要幫上售票點(diǎn)和座位號(hào) 10 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count); 11 //為了防止機(jī)子的性能太好,資源都都給一個(gè)線程搶光了,就休眠一段時(shí)間 12 Thread.Sleep(500); 13 } 14 }

?

這里開(kāi)三個(gè)線程,模擬三個(gè)售票點(diǎn)去賣(mài)這10張票

1 private void MutexTest() 2 { 3 count = 0; 4 Thread t1 = new Thread(ThreadingCount2); 5 Thread t2 = new Thread(ThreadingCount2); 6 Thread t3 = new Thread(ThreadingCount2); 7 t1.Start(); 8 t2.Start(); 9 t3.Start(); 10 }

?

運(yùn)行結(jié)果

?

Semaphore?(限制可同時(shí)訪問(wèn)某一資源或資源池的線程數(shù))

  這個(gè)稱(chēng)之為信號(hào)量,也有些人叫它作信號(hào)燈。這個(gè)概念倒是在操作系統(tǒng)中聽(tīng)過(guò),現(xiàn)在用起來(lái)就感覺(jué)可以通過(guò)信號(hào)量來(lái)限制進(jìn)入某段區(qū)域的次數(shù),通過(guò)調(diào)用WaitOne和Release方法,這個(gè)挺適合生產(chǎn)者與消費(fèi)者那個(gè)問(wèn)題的。記得解決生產(chǎn)者與消費(fèi)者的問(wèn)題上有用到這個(gè)信號(hào)量。下面則是MSDN的說(shuō)明:

  使用?Semaphore?類(lèi)可控制對(duì)資源池的訪問(wèn)。?線程通過(guò)調(diào)用?WaitOne?方法(從?WaitHandle?類(lèi)繼承)進(jìn)入信號(hào)量,并通過(guò)調(diào)用?Release?方法釋放信號(hào)量。

  信號(hào)量的計(jì)數(shù)在每次線程進(jìn)入信號(hào)量時(shí)減小,在線程釋放信號(hào)量時(shí)增加。?當(dāng)計(jì)數(shù)為零時(shí),后面的請(qǐng)求將被阻塞,直到有其他線程釋放信號(hào)量。?當(dāng)所有的線程都已釋放信號(hào)量時(shí),計(jì)數(shù)達(dá)到創(chuàng)建信號(hào)量時(shí)所指定的最大值。

  被阻止的線程并不一定按特定的順序(如 FIFO 或 LIFO)進(jìn)入信號(hào)量。

  下面則用Begin/End模型來(lái)作為例子,它這不停地交替輸出Begin和End,每輸出一次Begin,就會(huì)暫停,直到輸出了一次End,才會(huì)輸出下一個(gè)Begin。用兩個(gè)線程,一個(gè)是專(zhuān)門(mén)輸出Begin的;另一個(gè)是輸出End的。

Begin的線程方法如下

1 private void Begin() 2 { 3 while (true) 4 { 5 semaphore.WaitOne(); 6 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+ " Begin"); 7 } 8 }

?

  這里先是等待信號(hào)才去輸出,這個(gè)輸出就相當(dāng)于進(jìn)行某一些操作了,如果把Waitone放到輸出的后面,就限制不了對(duì)某個(gè)操作進(jìn)行次數(shù)限制。當(dāng)然,這樣做的話,對(duì)semaphore對(duì)象構(gòu)造時(shí)也會(huì)不同。

End的線程方法如下

1 private void End() 2 { 3 while (true) 4 { 5 Thread.Sleep(1000); 6 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End"); 7 semaphore.Release(); 8 } 9 }

  這里休眠1秒作用有兩個(gè),第一是等待Begin先運(yùn)行才釋放信號(hào),第二是控制輸出的節(jié)奏,免得屏幕上猛的刷一大堆Begin/End,看不清什么東西了。

  Semaphore構(gòu)造時(shí)是這樣的semaphore = new Semaphore(1, 1);第一個(gè)參數(shù)是初始化時(shí)的信號(hào)量,第二個(gè)參數(shù)是總的信號(hào)量,調(diào)用則是這樣,兩個(gè)線程輸出Begin,一個(gè)線程數(shù)據(jù)End

1 Thread t1 = new Thread(Begin); 2 Thread t3 = new Thread(Begin); 3 Thread t2 = new Thread(End); 4 t1.Start(); 5 t3.Start(); 6 t2.Start();

  運(yùn)行的結(jié)果,兩個(gè)線程會(huì)搶著輸出Begin,輸出了Begin之后就會(huì)被阻塞,等到End輸出了之后才能進(jìn)行下一次爭(zhēng)奪Begin的輸出

有位園友說(shuō),我老是用那個(gè)Sleep方法不好,于是這里就給一個(gè)沒(méi)有用Sleep方法的Begin/End版本。

用到的信號(hào)量就要兩個(gè)了,一個(gè)是用于阻塞Begin的,一個(gè)是用于阻塞End的,初始時(shí)值也有出入。End的要讓它先阻塞,Begin的要讓它先通過(guò)

1 private Semaphore semBegin, semEnd; 2 semBegin = new Semaphore(1, 1); 3 semEnd = new Semaphore(0, 1);

?

1 private void Begin() 2 { 3 for (int i = 0; i < 5; i++) 4 { 5 semBegin.WaitOne(); 6 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : Begin "); 7 semEnd.Release(); 8 } 9 } 10 11 private void End() 12 { 13 while (true) 14 { 15 semEnd.WaitOne(); 16 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : End "); 17 semBegin.Release(); 18 } 19 } 20 }

?

這樣使用信號(hào)量有死鎖的嫌疑,但是實(shí)踐過(guò)是沒(méi)有的。運(yùn)行結(jié)果與之前的一樣,暫時(shí)不考慮信號(hào)量的關(guān)閉與線程關(guān)閉等問(wèn)題。

?

Mutex?(一個(gè)同步基元,也可用于進(jìn)程間同步)

  這個(gè)稱(chēng)之為互斥體。這個(gè)互斥體跟lock關(guān)鍵字差不多,是保證某片代碼區(qū)域只能給一個(gè)線程訪問(wèn),通過(guò)調(diào)用WaitOne來(lái)掛起線程等待信號(hào)和ReleaseMutex釋放一次互斥信號(hào)來(lái)喚醒當(dāng)前線程這樣的方式來(lái)實(shí)現(xiàn)。這個(gè)掛起只會(huì)掛起后來(lái)進(jìn)入這片區(qū)域的線程,最初的線程在喚醒之前無(wú)論遇到多少個(gè)WaitOne照樣過(guò),不過(guò)在之前WaitOne了多少次,到后來(lái)就要相應(yīng)釋放那么多次,否則別的線程一直被掛起到某個(gè)WaitOne處,雖然把等待和釋放分開(kāi)了兩個(gè)方法,但放在不同線程去調(diào)用的話只會(huì)拋異常,因?yàn)檫@兩個(gè)方法要在一個(gè)同步的區(qū)域內(nèi)調(diào)用的。下面則是MSDN的說(shuō)明。

  當(dāng)兩個(gè)或更多線程需要同時(shí)訪問(wèn)一個(gè)共享資源時(shí),系統(tǒng)需要使用同步機(jī)制來(lái)確保一次只有一個(gè)線程使用該資源。?Mutex?是同步基元,它只向一個(gè)線程授予對(duì)共享資源的獨(dú)占訪問(wèn)權(quán)。?如果一個(gè)線程獲取了互斥體,則要獲取該互斥體的第二個(gè)線程將被掛起,直到第一個(gè)線程釋放該互斥體。

  既然這個(gè)互斥體的用法跟lock那么相像,我用搶車(chē)票的例子吧!這里變的只是線程的方法而已,創(chuàng)建線程的跟原來(lái)的一樣,不再重復(fù)粘貼了

1 private void ThreadingCount() 2 { 3 while (true) 4 { 5 mutex.WaitOne(); 6 if (count > 10) 7 { 8 mutex.ReleaseMutex(); 9 break; 10 } 11 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count++); 12 mutex.ReleaseMutex(); 13 Thread.Sleep(500); 14 } 15 }

?

構(gòu)造對(duì)象時(shí)這樣mutex = new Mutex();,運(yùn)行結(jié)果如下

?

ManualResetEvent(通知一個(gè)或多個(gè)正在等待的線程已發(fā)生事件)與AutoResetEvent?(通知正在等待的線程已發(fā)生事件)

  這兩個(gè)類(lèi)很相似,都是調(diào)用了WaitOne就阻塞當(dāng)前線程等待信號(hào),直到調(diào)用了Set才發(fā)了信號(hào)喚醒阻塞的線程。不同點(diǎn)就在調(diào)用Set方法之后了,AutoResetEvent?只是喚醒一個(gè)線程,但是就喚醒了所有等待信號(hào)而阻塞的線程,并且需要調(diào)用Reset關(guān)閉了信號(hào),才能使WaitOne處能阻塞線程。下面分別是MSDN上對(duì)它們的描述

?????? ManualResetEvent?使線程可以通過(guò)發(fā)信號(hào)來(lái)互相通信。?通常,此通信涉及一個(gè)線程在其他線程進(jìn)行之前必須完成的任務(wù)。

?????? AutoResetEvent?使線程可以通過(guò)發(fā)信號(hào)來(lái)互相通信。?通常,此通信涉及線程需要獨(dú)占訪問(wèn)的資源。

?????? 這里就用Begin/End的作例子

兩個(gè)類(lèi)用起來(lái)基本一樣,就效果一樣而已,出于篇幅的考慮,只上一次代碼算了

1 private void Begin() 2 { 3 while (true) 4 { 5 //等待信號(hào) 6 //autoreset.WaitOne(); 7 manualreset.WaitOne(); 8 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+ " Begin"); 9 //關(guān)閉信號(hào) 10 manualreset.Reset(); 11 //這里對(duì)于autorest來(lái)說(shuō)其實(shí)可以需要 12 //因?yàn)檎{(diào)用Set()之后就會(huì)關(guān)閉信號(hào)了 13 //autoreset.Reset(); 14 } 15 } 16 17 private void End() 18 { 19 while (true) 20 { 21 Thread.Sleep(1000); 22 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End"); 23 //semaphore.Release(); 24 manualreset.Set(); 25 //autoreset.Set(); 26 } 27 }

?

  阻塞線程和輸出Begin的道理和前面使用Semaphore?的一樣,都是為了確保能互斥地執(zhí)行那個(gè)操作,可是對(duì)于使用ManualResetEvent?就不是這樣說(shuō)了,看看結(jié)果就知道了

  這個(gè)是ManualResetEvent?的運(yùn)行結(jié)果,一發(fā)出了信號(hào),之前等待信號(hào)的兩個(gè)線程都同時(shí)被喚醒了,一齊去輸出Begin,兩個(gè)線程又在關(guān)閉信號(hào)之后阻塞在等待信號(hào)的地方。

  而AutoResetEvent?的結(jié)果則不同,Begin和End都是一個(gè)挨著一個(gè)交替輸出,那個(gè)線程搶到了信號(hào)就能輸出Begin,搶不到的就一直阻塞在那里。

對(duì)了,兩個(gè)對(duì)象的構(gòu)造如下

manualreset = new ManualResetEvent(true);autoreset = new AutoResetEvent(true);

true是初始狀態(tài),true就一開(kāi)始有信號(hào),免得沒(méi)信號(hào)就一直卡在那里,要等End執(zhí)行了才放行,這樣有了End才有Begin就不對(duì)了。

這里也同樣給出不用Sleep的版本,同樣所需要的對(duì)象也比原本的多了

1 private ManualResetEvent manBegin, manEnd; 2 3 private AutoResetEvent autoBegin, autoEnd; 4 5 manBegin = new ManualResetEvent(true); 6 manEnd = new ManualResetEvent(false); 7 8 autoBegin = new AutoResetEvent(true); 9 autoEnd = new AutoResetEvent(false);

?

初始狀態(tài)跟上面使用信號(hào)量的道理一樣。

1 private void Begin() 2 { 3 for (int i = 0; i < 5; i++) 4 { 5 manBegin.WaitOne(); 6 //manBegin.Reset();//在這里Reset就只能是一個(gè)Begin一個(gè)End 7 //autoBegin.WaitOne(); 8 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : Begin "); 9 manBegin.Reset();//在這里Reset就兩個(gè)Begin一個(gè)End 10 manEnd.Set(); 11 //autoEnd.Set(); 12 } 13 } 14 15 private void End() 16 { 17 while (true) 18 { 19 manEnd.WaitOne(); 20 manEnd.Reset(); 21 //autoEnd.WaitOne(); 22 Console.WriteLine(Thread.CurrentThread.ManagedThreadId+" : End "); 23 manBegin.Set(); 24 //autoBegin.Set(); 25 } 26 } 27 }

?

這里使用ManualResetEvent類(lèi)的時(shí)候有兩種情況,注釋中有說(shuō)明,關(guān)閉信號(hào)的地方不同,會(huì)影響到Begin輸出的數(shù)量,在這里也用ManualResetEvent類(lèi)實(shí)現(xiàn)Begin和End間隔輸出。

?

Monitor提供同步訪問(wèn)對(duì)象的機(jī)制

  這個(gè)類(lèi)是在網(wǎng)上看別人的博文時(shí)看到的,這個(gè)類(lèi)比較原始。還是先看看MSDN的說(shuō)明吧!

  Monitor類(lèi)通過(guò)向單個(gè)線程授予對(duì)象鎖來(lái)控制對(duì)對(duì)象的訪問(wèn)。?對(duì)象鎖提供限制訪問(wèn)代碼塊(通常稱(chēng)為臨界區(qū))的能力。?當(dāng)一個(gè)線程擁有對(duì)象的鎖時(shí),其他任何線程都不能獲取該鎖。?還可以使用?Monitor?來(lái)確保不會(huì)允許其他任何線程訪問(wèn)正在由鎖的所有者執(zhí)行的應(yīng)用程序代碼節(jié),除非另一個(gè)線程正在使用其他的鎖定對(duì)象執(zhí)行該代碼。

  Enter方法和Exit方法已經(jīng)被封裝成lock關(guān)鍵字了。這里也給個(gè)使用EnterExit方法的例子,搶票問(wèn)題的

1 private void ThreadingCount() 2 { 3 while (true) 4 { 5 Monitor.Enter(objFlag); 6 if (count > 10) 7 { 8 Monitor.Exit(objFlag); 9 break; 10 } 11 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + count++); 12 Thread.Sleep(500); 13 Monitor.Exit(objFlag); 14 } 15 }

?

  Enter和Exit方法都要傳一個(gè)object類(lèi)型的參數(shù),作用就跟lock的鎖旗標(biāo)一樣。

  Monitor除了能實(shí)現(xiàn)搶票這類(lèi)的問(wèn)題外,同樣也能解決Begin/End的問(wèn)題的。它有個(gè)Wait和Pluse方法。下面則列舉出另一個(gè)例子的代碼

1 private void Begin() 2 { 3 lock (objFlag) 4 { 5 Monitor.Pulse(objFlag); 6 } 7 while (true) 8 { 9 lock (objFlag) 10 { 11 //調(diào)用Wait方法釋放對(duì)象上的鎖并阻止該線程(線程狀態(tài)為WaitSleepJoin) 12 //該線程進(jìn)入到同步對(duì)象的等待隊(duì)列,直到其它線程調(diào)用Pulse使該線程進(jìn)入到就緒隊(duì)列中 13 //線程進(jìn)入到就緒隊(duì)列中才有條件爭(zhēng)奪同步對(duì)象的所有權(quán) 14 //如果沒(méi)有其它線程調(diào)用Pulse/PulseAll方法,該線程不可能被執(zhí)行 15 Monitor.Wait(objFlag); 16 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Begin"); 17 } 18 } 19 } 20 21 private void End() 22 { 23 Thread.Sleep(1000); 24 while (true) 25 { 26 lock (objFlag) 27 { 28 //通知等待隊(duì)列中的線程鎖定對(duì)象狀態(tài)的更改,但不會(huì)釋放鎖 29 //接收到Pulse脈沖后,線程從同步對(duì)象的等待隊(duì)列移動(dòng)到就緒隊(duì)列中 30 //注意:最終能獲得鎖的線程并不一定是得到Pulse脈沖的線程 31 Monitor.Pulse(objFlag); 32 33 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " End"); 34 //釋放對(duì)象上的鎖并阻止當(dāng)前線程,直到它重新獲取該鎖 35 //如果指定的超時(shí)間隔已過(guò),則線程進(jìn)入就緒隊(duì)列 36 Monitor.Wait(objFlag, 1000); 37 } 38 } 39 } 40 }

  當(dāng)然這個(gè)例子其實(shí)挺生搬硬套的,為了讓Begin先輸出,就Pluse一次,同時(shí)又讓End的線程休眠。如果Begin的線程不運(yùn)行,End的照樣能正常輸出,這里希望各位有什么高見(jiàn)的不要吝嗇,盡管提出來(lái)。下面是運(yùn)行結(jié)果。

  上面如果有什么不足的或遺漏的或說(shuō)錯(cuò)的,請(qǐng)各位盡情指出。謝謝!

轉(zhuǎn)載于:https://www.cnblogs.com/HopeGi/archive/2013/05/08/3066129.html

總結(jié)

以上是生活随笔為你收集整理的多线程的那群“象”的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。