C#多线程和线程池
.Net的公用語(yǔ)言運(yùn)行時(shí)(Common Language Runtime,CLR)能區(qū)分兩種不同類(lèi)型的線(xiàn)程:前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程。這兩者的區(qū)別就是:應(yīng)用程序必須運(yùn)行完所有的前臺(tái)線(xiàn)程才可以退出;而對(duì)于后臺(tái)線(xiàn)程,應(yīng)用程序則可以不考慮其是否已經(jīng)運(yùn)行完畢而直接退出,所有的后臺(tái)線(xiàn)程在應(yīng)用程序退出時(shí)都會(huì)自動(dòng)結(jié)束。
前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程的區(qū)別和聯(lián)系:
1、后臺(tái)線(xiàn)程不會(huì)阻止進(jìn)程的終止。屬于某個(gè)進(jìn)程的所有前臺(tái)線(xiàn)程都終止后,該進(jìn)程就會(huì)被終止。所有剩余的后臺(tái)線(xiàn)程都會(huì)停止且不會(huì)完成。
2、可以在任何時(shí)候?qū)⑶芭_(tái)線(xiàn)程修改為后臺(tái)線(xiàn)程,方式是設(shè)置Thread.IsBackground 屬性。
3、不管是前臺(tái)線(xiàn)程還是后臺(tái)線(xiàn)程,如果線(xiàn)程內(nèi)出現(xiàn)了異常,都會(huì)導(dǎo)致進(jìn)程的終止。
4、托管線(xiàn)程池中的線(xiàn)程都是后臺(tái)線(xiàn)程,使用new Thread方式創(chuàng)建的線(xiàn)程默認(rèn)都是前臺(tái)線(xiàn)程。
說(shuō)明:???
????????應(yīng)用程序的主線(xiàn)程以及使用Thread構(gòu)造的線(xiàn)程都默認(rèn)為前臺(tái)線(xiàn)程
通過(guò)BeginXXX方法運(yùn)行的線(xiàn)程都是后臺(tái)線(xiàn)程。
線(xiàn)程池線(xiàn)程也就是使用 ThreadPool.QueueUserWorkItem()和Task工廠(chǎng)創(chuàng)建的線(xiàn)程都默認(rèn)為后臺(tái)線(xiàn)程
前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程適合的場(chǎng)合
???????通常,后臺(tái)線(xiàn)程非常適合于完成后臺(tái)任務(wù),應(yīng)該將被動(dòng)偵聽(tīng)活動(dòng)的線(xiàn)程設(shè)置為后臺(tái)線(xiàn)程,而將負(fù)責(zé)發(fā)送數(shù)據(jù)的線(xiàn)程設(shè)置為前臺(tái)線(xiàn)程,這樣,在所有的數(shù)據(jù)發(fā)送完畢之前該線(xiàn)程不會(huì)被終止。
一般前臺(tái)線(xiàn)程用于需要長(zhǎng)時(shí)間等待的任務(wù),比如監(jiān)聽(tīng)客戶(hù)端的請(qǐng)求;后臺(tái)線(xiàn)程一般用于處理時(shí)間較短的任務(wù),比如處理客戶(hù)端發(fā)過(guò)來(lái)的請(qǐng)求信息。
?
1、概念
?1.0 線(xiàn)程的和進(jìn)程的關(guān)系以及優(yōu)缺點(diǎn)
windows系統(tǒng)是一個(gè)多線(xiàn)程的操作系統(tǒng)。一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程。進(jìn)程是線(xiàn)程的容器,一個(gè)C#客戶(hù)端程序開(kāi)始于一個(gè)單獨(dú)的線(xiàn)程,CLR(公共語(yǔ)言運(yùn)行庫(kù))為該進(jìn)程創(chuàng)建了一個(gè)線(xiàn)程,該線(xiàn)程稱(chēng)為主線(xiàn)程。例如當(dāng)我們創(chuàng)建一個(gè)C#控制臺(tái)程序,程序的入口是Main()函數(shù),Main()函數(shù)是始于一個(gè)主線(xiàn)程的。它的功能主要 是產(chǎn)生新的線(xiàn)程和執(zhí)行程序。C#是一門(mén)支持多線(xiàn)程的編程語(yǔ)言,通過(guò)Thread類(lèi)創(chuàng)建子線(xiàn)程,引入using System.Threading命名空間。?
多線(xiàn)程的優(yōu)點(diǎn):?
| 1 2 | 1、 多線(xiàn)程可以提高CPU的利用率,因?yàn)楫?dāng)一個(gè)線(xiàn)程處于等待狀態(tài)的時(shí)候,CPU會(huì)去執(zhí)行另外的線(xiàn)程 2、 提高了CPU的利用率,就可以直接提高程序的整體執(zhí)行速度 |
多線(xiàn)程的缺點(diǎn):
?
| 1 2 3 | 1、線(xiàn)程開(kāi)的越多,內(nèi)存占用越大 2、協(xié)調(diào)和管理代碼的難度加大,需要CPU時(shí)間跟蹤線(xiàn)程 3、線(xiàn)程之間對(duì)資源的共享可能會(huì)產(chǎn)生可不遇知的問(wèn)題 |
?
? ? ?1.1 前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程
? ? ?C#中的線(xiàn)程分為前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程,線(xiàn)程創(chuàng)建時(shí)不做設(shè)置默認(rèn)是前臺(tái)線(xiàn)程。即線(xiàn)程屬性IsBackground=false。
Thread.IsBackground = false;//false:設(shè)置為前臺(tái)線(xiàn)程,系統(tǒng)默認(rèn)為前臺(tái)線(xiàn)程。?區(qū)別以及如何使用:
????這兩者的區(qū)別就是:應(yīng)用程序必須運(yùn)行完所有的前臺(tái)線(xiàn)程才可以退出;而對(duì)于后臺(tái)線(xiàn)程,應(yīng)用程序則可以不考慮其是否已經(jīng)運(yùn)行完畢而直接退出,所有的后臺(tái)線(xiàn)程在應(yīng)用程序退出時(shí)都會(huì)自動(dòng)結(jié)束。一般后臺(tái)線(xiàn)程用于處理時(shí)間較短的任務(wù),如在一個(gè)Web服務(wù)器中可以利用后臺(tái)線(xiàn)程來(lái)處理客戶(hù)端發(fā)過(guò)來(lái)的請(qǐng)求信息。而前臺(tái)線(xiàn)程一般用于處理需要長(zhǎng)時(shí)間等待的任務(wù),如在Web服務(wù)器中的監(jiān)聽(tīng)客戶(hù)端請(qǐng)求的程序。
線(xiàn)程是寄托在進(jìn)程上的,進(jìn)程都結(jié)束了,線(xiàn)程也就不復(fù)存在了!
只要有一個(gè)前臺(tái)線(xiàn)程未退出,進(jìn)程就不會(huì)終止!即說(shuō)的就是程序不會(huì)關(guān)閉!(即在資源管理器中可以看到進(jìn)程未結(jié)束。)
? ? ?1.3 多線(xiàn)程的創(chuàng)建
? ? 下面的代碼創(chuàng)建了一個(gè)子線(xiàn)程,作為程序的入口mian()函數(shù)所在的線(xiàn)程即為主線(xiàn)程,我們通過(guò)Thread類(lèi)來(lái)創(chuàng)建子線(xiàn)程,Thread類(lèi)有?ThreadStart 和 ParameterizedThreadStart類(lèi)型的委托參數(shù),我們也可以直接寫(xiě)方法的名字。線(xiàn)程執(zhí)行的方法可以傳遞參數(shù)(可選),參數(shù)的類(lèi)型為object,寫(xiě)在Start()里。
class Program{//我們的控制臺(tái)程序入口是main函數(shù)。它所在的線(xiàn)程即是主線(xiàn)程static void Main(string[] args) {Thread thread = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法thread.Name = "子線(xiàn)程";//thread.Start("王建"); //在此方法內(nèi)傳遞參數(shù),類(lèi)型為object,發(fā)送和接收涉及到拆裝箱操作thread.Start(); Console.ReadKey();}public static void ThreadMethod(object parameter) //方法內(nèi)可以有參數(shù),也可以沒(méi)有參數(shù){Console.WriteLine("{0}開(kāi)始執(zhí)行。", Thread.CurrentThread.Name);}}首先使用new Thread()創(chuàng)建出新的線(xiàn)程,然后調(diào)用Start方法使得線(xiàn)程進(jìn)入就緒狀態(tài),得到系統(tǒng)資源后就執(zhí)行,在執(zhí)行過(guò)程中可能有等待、休眠、死亡和阻塞四種狀態(tài)。正常執(zhí)行結(jié)束時(shí)間片后返回到就緒狀態(tài)。如果調(diào)用Suspend方法會(huì)進(jìn)入等待狀態(tài),調(diào)用Sleep或者遇到進(jìn)程同步使用的鎖機(jī)制而休眠等待。具體過(guò)程如下圖所示:
2、線(xiàn)程的基本操作
線(xiàn)程和其它常見(jiàn)的類(lèi)一樣,有著很多屬性和方法,參考下表:
2.1 線(xiàn)程的相關(guān)屬性
我們可以通過(guò)上面表中的屬性獲取線(xiàn)程的一些相關(guān)信息,下面是代碼展示和輸出結(jié)果:
static void Main(string[] args) {Thread thread = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法thread.Name = "子線(xiàn)程"; thread.Start();StringBuilder threadInfo = new StringBuilder();threadInfo.Append(" 線(xiàn)程當(dāng)前的執(zhí)行狀態(tài): " + thread.IsAlive);threadInfo.Append("\n 線(xiàn)程當(dāng)前的名字: " + thread.Name);threadInfo.Append("\n 線(xiàn)程當(dāng)前的優(yōu)先級(jí): " + thread.Priority);threadInfo.Append("\n 線(xiàn)程當(dāng)前的狀態(tài): " + thread.ThreadState);Console.Write(threadInfo);Console.ReadKey();}public static void ThreadMethod(object parameter) {Console.WriteLine("{0}開(kāi)始執(zhí)行。", Thread.CurrentThread.Name);}?輸輸出結(jié)果:
2.2 線(xiàn)程的相關(guān)操作
2.2.1?Abort()方法
Abort()方法用來(lái)終止線(xiàn)程,調(diào)用此方法強(qiáng)制停止正在執(zhí)行的線(xiàn)程,它會(huì)拋出一個(gè)ThreadAbortException異常從而導(dǎo)致目標(biāo)線(xiàn)程的終止。下面代碼演示:
static void Main(string[] args) {Thread thread = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 thread.Name = "小A";thread.Start(); Console.ReadKey();}public static void ThreadMethod(object parameter) {Console.WriteLine("我是:{0},我要終止了", Thread.CurrentThread.Name);//開(kāi)始終止線(xiàn)程Thread.CurrentThread.Abort();//下面的代碼不會(huì)執(zhí)行for (int i = 0; i < 10; i++){Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name,i);}}
執(zhí)行結(jié)果:和我們想象的一樣,下面的循環(huán)沒(méi)有被執(zhí)行
?
2.2.2?ResetAbort()方法
? Abort方法可以通過(guò)跑出ThreadAbortException異常中止線(xiàn)程,而使用ResetAbort方法可以取消中止線(xiàn)程的操作,下面通過(guò)代碼演示使用?ResetAbort方法。
static void Main(string[] args) {Thread thread = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 thread.Name = "小A";thread.Start(); Console.ReadKey();}public static void ThreadMethod(object parameter) {try{Console.WriteLine("我是:{0},我要終止了", Thread.CurrentThread.Name); //開(kāi)始終止線(xiàn)程Thread.CurrentThread.Abort();}catch(ThreadAbortException ex){Console.WriteLine("我是:{0},我又恢復(fù)了", Thread.CurrentThread.Name);//恢復(fù)被終止的線(xiàn)程Thread.ResetAbort();}for (int i = 0; i < 10; i++){Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name,i);}}執(zhí)行結(jié)果:
2.2.3?Sleep()方法?
? Sleep()方法調(diào)已阻塞線(xiàn)程,是當(dāng)前線(xiàn)程進(jìn)入休眠狀態(tài),在休眠過(guò)程中占用系統(tǒng)內(nèi)存但是不占用系統(tǒng)時(shí)間,當(dāng)休眠期過(guò)后,繼續(xù)執(zhí)行,聲明如下: ?
public static void Sleep(TimeSpan timeout); //時(shí)間段public static void Sleep(int millisecondsTimeout); //毫秒數(shù)實(shí)例代碼:?
static void Main(string[] args){Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "小A";threadA.Start();Console.ReadKey();} public static void ThreadMethod(object parameter) { for (int i = 0; i < 10; i++){ Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name,i);Thread.Sleep(300); //休眠300毫秒 }}將上面的代碼執(zhí)行以后,可以清楚的看到每次循環(huán)之間相差300毫秒的時(shí)間。
? ? ? 2.2.4?join()方法
?Join方法主要是用來(lái)阻塞調(diào)用線(xiàn)程,直到某個(gè)線(xiàn)程終止或經(jīng)過(guò)了指定時(shí)間為止。官方的解釋比較乏味,通俗的說(shuō)就是創(chuàng)建一個(gè)子線(xiàn)程,給它加了這個(gè)方法,其它線(xiàn)程就會(huì)暫停執(zhí)行,直到這個(gè)線(xiàn)程執(zhí)行完為止才去執(zhí)行(包括主線(xiàn)程)。她的方法聲明如下:
public void Join();public bool Join(int millisecondsTimeout); //毫秒數(shù)public bool Join(TimeSpan timeout); //時(shí)間段為了驗(yàn)證上面所說(shuō)的,我們首先看一段代碼: ?
static void Main(string[] args){Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "小A";Thread threadB = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadB.Name = "小B";threadA.Start();//threadA.Join(); threadB.Start();//threadB.Join();for (int i = 0; i < 10; i++){ Console.WriteLine("我是:主線(xiàn)程,我循環(huán){1}次", Thread.CurrentThread.Name, i);Thread.Sleep(300); //休眠300毫秒 }Console.ReadKey();} public static void ThreadMethod(object parameter) { for (int i = 0; i < 10; i++){ Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name,i);Thread.Sleep(300); //休眠300毫秒 }}?
因?yàn)榫€(xiàn)程之間的執(zhí)行是隨機(jī)的,所有執(zhí)行結(jié)果和我們想象的一樣,雜亂無(wú)章!但是說(shuō)明他們是同時(shí)執(zhí)行的。
? ? ?現(xiàn)在我們把代碼中的 ?ThreadA.join()方法注釋取消,首先程序中有三個(gè)線(xiàn)程,ThreadA、ThreadB和主線(xiàn)程,首先主線(xiàn)程先阻塞,然后線(xiàn)程ThreadB阻塞,ThreadA先執(zhí)行,執(zhí)行完畢以后ThreadB接著執(zhí)行,最后才是主線(xiàn)程執(zhí)行。
看執(zhí)行結(jié)果:
?
? ? ? ? 2.2.5?Suspent()和Resume()方法
?? ? ? 其實(shí)在C# 2.0以后, Suspent()和Resume()方法已經(jīng)過(guò)時(shí)了。suspend()方法容易發(fā)生死鎖。調(diào)用suspend()的時(shí)候,目標(biāo)線(xiàn)程會(huì)停下來(lái),但卻仍然持有在這之前獲得的鎖定。此時(shí),其他任何線(xiàn)程都不能訪(fǎng)問(wèn)鎖定的資源,除非被”掛起”的線(xiàn)程恢復(fù)運(yùn)行。對(duì)任何線(xiàn)程來(lái)說(shuō),如果它們想恢復(fù)目標(biāo)線(xiàn)程,同時(shí)又試圖使用任何一個(gè)鎖定的資源,就會(huì)造成死鎖。所以不應(yīng)該使用suspend()。
?
static void Main(string[] args){Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "小A"; threadA.Start(); Thread.Sleep(3000); //休眠3000毫秒 threadA.Resume(); //繼續(xù)執(zhí)行已經(jīng)掛起的線(xiàn)程Console.ReadKey();}public static void ThreadMethod(object parameter){Thread.CurrentThread.Suspend(); //掛起當(dāng)前線(xiàn)程for (int i = 0; i < 10; i++){Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name, i); }}?
? ? ? ?執(zhí)行上面的代碼。窗口并沒(méi)有馬上執(zhí)行 ThreadMethod方法輸出循環(huán)數(shù)字,而是等待了三秒鐘之后才輸出,因?yàn)榫€(xiàn)程開(kāi)始執(zhí)行的時(shí)候執(zhí)行了Suspend()方法掛起。然后主線(xiàn)程休眠了3秒鐘以后又通過(guò)Resume()方法恢復(fù)了線(xiàn)程threadA。
? ? 2.2.6 線(xiàn)程的優(yōu)先級(jí)
如果在應(yīng)用程序中有多個(gè)線(xiàn)程在運(yùn)行,但一些線(xiàn)程比另一些線(xiàn)程重要,這種情況下可以在一個(gè)進(jìn)程中為不同的線(xiàn)程指定不同的優(yōu)先級(jí)。線(xiàn)程的優(yōu)先級(jí)可以通過(guò)Thread類(lèi)Priority屬性設(shè)置,Priority屬性是一個(gè)ThreadPriority型枚舉,列舉了5個(gè)優(yōu)先等級(jí):AboveNormal、BelowNormal、Highest、Lowest、Normal。公共語(yǔ)言運(yùn)行庫(kù)默認(rèn)是Normal類(lèi)型的。見(jiàn)下圖:
直接上代碼來(lái)看效果:
?View Code
執(zhí)行結(jié)果:
上面的代碼中有三個(gè)線(xiàn)程,threadA,threadB和主線(xiàn)程,threadA優(yōu)先級(jí)最高,threadB優(yōu)先級(jí)最低。這一點(diǎn)從運(yùn)行結(jié)果中也可以看出,線(xiàn)程B 偶爾會(huì)出現(xiàn)在主線(xiàn)程和線(xiàn)程A前面。當(dāng)有多個(gè)線(xiàn)程同時(shí)處于可執(zhí)行狀態(tài),系統(tǒng)優(yōu)先執(zhí)行優(yōu)先級(jí)較高的線(xiàn)程,但這只意味著優(yōu)先級(jí)較高的線(xiàn)程占有更多的CPU時(shí)間,并不意味著一定要先執(zhí)行完優(yōu)先級(jí)較高的線(xiàn)程,才會(huì)執(zhí)行優(yōu)先級(jí)較低的線(xiàn)程。
優(yōu)先級(jí)越高表示CPU分配給該線(xiàn)程的時(shí)間片越多,執(zhí)行時(shí)間就多
優(yōu)先級(jí)越低表示CPU分配給該線(xiàn)程的時(shí)間片越少,執(zhí)行時(shí)間就少
? ?3、線(xiàn)程同步
什么是線(xiàn)程安全:
線(xiàn)程安全是指在當(dāng)一個(gè)線(xiàn)程訪(fǎng)問(wèn)該類(lèi)的某個(gè)數(shù)據(jù)時(shí),進(jìn)行保護(hù),其他線(xiàn)程不能進(jìn)行訪(fǎng)問(wèn)直到該線(xiàn)程讀取完,其他線(xiàn)程才可使用。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。
線(xiàn)程有可能和其他線(xiàn)程共享一些資源,比如,內(nèi)存,文件,數(shù)據(jù)庫(kù)等。當(dāng)多個(gè)線(xiàn)程同時(shí)讀寫(xiě)同一份共享資源的時(shí)候,可能會(huì)引起沖突。這時(shí)候,我們需要引入線(xiàn)程“同步”機(jī)制,即各位線(xiàn)程之間要有個(gè)先來(lái)后到,不能一窩蜂擠上去搶作一團(tuán)。線(xiàn)程同步的真實(shí)意思和字面意思恰好相反。線(xiàn)程同步的真實(shí)意思,其實(shí)是“排隊(duì)”:幾個(gè)線(xiàn)程之間要排隊(duì),一個(gè)一個(gè)對(duì)共享資源進(jìn)行操作,而不是同時(shí)進(jìn)行操作。
為什么要實(shí)現(xiàn)同步呢,下面的例子我們拿著名的單例模式來(lái)說(shuō)吧。看代碼
public class Singleton{private static Singleton instance; private Singleton() //私有函數(shù),防止實(shí)例{} public static Singleton GetInstance(){if (instance == null){instance = new Singleton();}return instance;}}? ? ? ?單例模式就是保證在整個(gè)應(yīng)用程序的生命周期中,在任何時(shí)刻,被指定的類(lèi)只有一個(gè)實(shí)例,并為客戶(hù)程序提供一個(gè)獲取該實(shí)例的全局訪(fǎng)問(wèn)點(diǎn)。但上面代碼有一個(gè)明顯的問(wèn)題,那就是假如兩個(gè)線(xiàn)程同時(shí)去獲取這個(gè)對(duì)象實(shí)例,那。。。。。。。。
我們隊(duì)代碼進(jìn)行修改:
public class Singleton {private static Singleton instance;private static object obj=new object(); private Singleton() //私有化構(gòu)造函數(shù){} public static Singleton GetInstance(){if(instance==null){lock(obj) //通過(guò)Lock關(guān)鍵字實(shí)現(xiàn)同步{if(instance==null){instance=new Singleton();}}}return instance;} }經(jīng)過(guò)修改后的代碼。加了一個(gè) lock(obj)代碼塊。這樣就能夠?qū)崿F(xiàn)同步了,假如不是很明白的話(huà),咱們看后面繼續(xù)講解~
3.0 使用Lock關(guān)鍵字實(shí)現(xiàn)線(xiàn)程同步?
首先創(chuàng)建兩個(gè)線(xiàn)程,兩個(gè)線(xiàn)程執(zhí)行同一個(gè)方法,參考下面的代碼:
static void Main(string[] args){Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "王文建";Thread threadB = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadB.Name = "生旭鵬";threadA.Start();threadB.Start();Console.ReadKey();}public static void ThreadMethod(object parameter){ for (int i = 0; i < 10; i++){Console.WriteLine("我是:{0},我循環(huán){1}次", Thread.CurrentThread.Name, i);Thread.Sleep(300);}}執(zhí)行結(jié)果:
?
通過(guò)上面的執(zhí)行結(jié)果,可以很清楚的看到,兩個(gè)線(xiàn)程是在同時(shí)執(zhí)行ThreadMethod這個(gè)方法,這顯然不符合我們線(xiàn)程同步的要求。我們對(duì)代碼進(jìn)行修改如下:
?View Code
執(zhí)行結(jié)果:
我們通過(guò)添加了 lock(this) {...}代碼,查看執(zhí)行結(jié)果實(shí)現(xiàn)了我們想要的線(xiàn)程同步需求。但是我們知道this表示當(dāng)前類(lèi)實(shí)例的本身,那么有這么一種情況,我們把需要訪(fǎng)問(wèn)的方法所在的類(lèi)型進(jìn)行兩個(gè)實(shí)例A和B,線(xiàn)程A訪(fǎng)問(wèn)實(shí)例A的方法ThreadMethod,線(xiàn)程B訪(fǎng)問(wèn)實(shí)例B的方法ThreadMethod,這樣的話(huà)還能夠達(dá)到線(xiàn)程同步的需求嗎。
?View Code
執(zhí)行結(jié)果:
我們會(huì)發(fā)現(xiàn),線(xiàn)程又沒(méi)有實(shí)現(xiàn)同步了!lock(this)對(duì)于這種情況是不行的!所以需要我們對(duì)代碼進(jìn)行修改!修改后的代碼如下:?
?View Code
通過(guò)查看執(zhí)行結(jié)果。會(huì)發(fā)現(xiàn)代碼實(shí)現(xiàn)了我們的需求。那么?lock(this) 和lock(Obj)有什么區(qū)別呢??
lock(this) 鎖定 當(dāng)前實(shí)例對(duì)象,如果有多個(gè)類(lèi)實(shí)例的話(huà),lock鎖定的只是當(dāng)前類(lèi)實(shí)例,對(duì)其它類(lèi)實(shí)例無(wú)影響。所有不推薦使用。 lock(typeof(Model))鎖定的是model類(lèi)的所有實(shí)例。 lock(obj)鎖定的對(duì)象是全局的私有化靜態(tài)變量。外部無(wú)法對(duì)該變量進(jìn)行訪(fǎng)問(wèn)。 lock 確保當(dāng)一個(gè)線(xiàn)程位于代碼的臨界區(qū)時(shí),另一個(gè)線(xiàn)程不進(jìn)入臨界區(qū)。如果其他線(xiàn)程試圖進(jìn)入鎖定的代碼,則它將一直等待(即被阻止),直到該對(duì)象被釋放。 所以,lock的結(jié)果好不好,還是關(guān)鍵看鎖的誰(shuí),如果外邊能對(duì)這個(gè)誰(shuí)進(jìn)行修改,lock就失去了作用。所以一般情況下,使用私有的、靜態(tài)的并且是只讀的對(duì)象。總結(jié):
1、lock的是必須是引用類(lèi)型的對(duì)象,string類(lèi)型除外。
2、lock推薦的做法是使用靜態(tài)的、只讀的、私有的對(duì)象。
3、保證lock的對(duì)象在外部無(wú)法修改才有意義,如果lock的對(duì)象在外部改變了,對(duì)其他線(xiàn)程就會(huì)暢通無(wú)阻,失去了lock的意義。
? ? ?不能鎖定字符串,鎖定字符串尤其危險(xiǎn),因?yàn)樽址还舱Z(yǔ)言運(yùn)行庫(kù) (CLR)“暫留”。 這意味著整個(gè)程序中任何給定字符串都只有一個(gè)實(shí)例,就是這同一個(gè)對(duì)象表示了所有運(yùn)行的應(yīng)用程序域的所有線(xiàn)程中的該文本。因此,只要在應(yīng)用程序進(jìn)程中的任何位置處具有相同內(nèi)容的字符串上放置了鎖,就將鎖定應(yīng)用程序中該字符串的所有實(shí)例。通常,最好避免鎖定 public 類(lèi)型或鎖定不受應(yīng)用程序控制的對(duì)象實(shí)例。例如,如果該實(shí)例可以被公開(kāi)訪(fǎng)問(wèn),則 lock(this) 可能會(huì)有問(wèn)題,因?yàn)椴皇芸刂频拇a也可能會(huì)鎖定該對(duì)象。這可能導(dǎo)致死鎖,即兩個(gè)或更多個(gè)線(xiàn)程等待釋放同一對(duì)象。出于同樣的原因,鎖定公共數(shù)據(jù)類(lèi)型(相比于對(duì)象)也可能導(dǎo)致問(wèn)題。而且lock(this)只對(duì)當(dāng)前對(duì)象有效,如果多個(gè)對(duì)象之間就達(dá)不到同步的效果。lock(typeof(Class))與鎖定字符串一樣,范圍太廣了。
3.1 使用Monitor類(lèi)實(shí)現(xiàn)線(xiàn)程同步?? ? ?
? ? ? Lock關(guān)鍵字是Monitor的一種替換用法,lock在IL代碼中會(huì)被翻譯成Monitor.?
? ? ?lock(obj)
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ?//代碼段
? ? ? ? ? ? ?}?
? ? 就等同于?
? ? Monitor.Enter(obj);?
? ? ? ? ? ? ? ? //代碼段
? ? Monitor.Exit(obj); ?
? ? ? ? ???Monitor的常用屬性和方法:
Enter(Object)?在指定對(duì)象上獲取排他鎖。
Exit(Object)?釋放指定對(duì)象上的排他鎖。?
?
Pulse?通知等待隊(duì)列中的線(xiàn)程鎖定對(duì)象狀態(tài)的更改。
PulseAll?通知所有的等待線(xiàn)程對(duì)象狀態(tài)的更改。
TryEnter(Object)?試圖獲取指定對(duì)象的排他鎖。
TryEnter(Object, Boolean)?嘗試獲取指定對(duì)象上的排他鎖,并自動(dòng)設(shè)置一個(gè)值,指示是否得到了該鎖。
Wait(Object)?釋放對(duì)象上的鎖并阻止當(dāng)前線(xiàn)程,直到它重新獲取該鎖。
? ? ? 常用的方法有兩個(gè),Monitor.Enter(object)方法是獲取鎖,Monitor.Exit(object)方法是釋放鎖,這就是Monitor最常用的兩個(gè)方法,在使用過(guò)程中為了避免獲取鎖之后因?yàn)楫惓?#xff0c;致鎖無(wú)法釋放,所以需要在try{} catch(){}之后的finally{}結(jié)構(gòu)體中釋放鎖(Monitor.Exit())。
Enter(Object)的用法很簡(jiǎn)單,看代碼?
static void Main(string[] args){ Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "A";Thread threadB = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadB.Name = "B";threadA.Start();threadB.Start();Thread.CurrentThread.Name = "C";ThreadMethod();Console.ReadKey();}static object obj = new object();public static void ThreadMethod(){Monitor.Enter(obj); //Monitor.Enter(obj) 鎖定對(duì)象try{for (int i = 0; i < 500; i++){Console.Write(Thread.CurrentThread.Name); }}catch(Exception ex){ }finally{ Monitor.Exit(obj); //釋放對(duì)象} }?
TryEnter(Object)和TryEnter()?方法在嘗試獲取一個(gè)對(duì)象上的顯式鎖方面和 Enter() 方法類(lèi)似。然而,它不像Enter()方法那樣會(huì)阻塞執(zhí)行。如果線(xiàn)程成功進(jìn)入關(guān)鍵區(qū)域那么TryEnter()方法會(huì)返回true.?和試圖獲取指定對(duì)象的排他鎖。看下面代碼演示:
? ? ? 我們可以通過(guò)Monitor.TryEnter(monster, 1000),該方法也能夠避免死鎖的發(fā)生,我們下面的例子用到的是該方法的重載,Monitor.TryEnter(Object,Int32),。?
static void Main(string[] args){ Thread threadA = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadA.Name = "A";Thread threadB = new Thread(ThreadMethod); //執(zhí)行的必須是無(wú)返回值的方法 threadB.Name = "B";threadA.Start();threadB.Start();Thread.CurrentThread.Name = "C";ThreadMethod();Console.ReadKey();}static object obj = new object();public static void ThreadMethod(){bool flag = Monitor.TryEnter(obj, 1000); //設(shè)置1S的超時(shí)時(shí)間,如果在1S之內(nèi)沒(méi)有獲得同步鎖,則返回false//上面的代碼設(shè)置了鎖定超時(shí)時(shí)間為1秒,也就是說(shuō),在1秒中后,//lockObj還未被解鎖,TryEntry方法就會(huì)返回false,如果在1秒之內(nèi),lockObj被解鎖,TryEntry返回true。我們可以使用這種方法來(lái)避免死鎖try{if (flag){for (int i = 0; i < 500; i++){Console.Write(Thread.CurrentThread.Name); }}}catch(Exception ex){}finally{if (flag)Monitor.Exit(obj);} }?Monitor.Wait和Monitor()Pause()
Wait(object)方法:釋放對(duì)象上的鎖并阻止當(dāng)前線(xiàn)程,直到它重新獲取該鎖,該線(xiàn)程進(jìn)入等待隊(duì)列。
?Pulse方法:只有鎖的當(dāng)前所有者可以使用?Pulse?向等待對(duì)象發(fā)出信號(hào),當(dāng)前擁有指定對(duì)象上的鎖的線(xiàn)程調(diào)用此方法以便向隊(duì)列中的下一個(gè)線(xiàn)程發(fā)出鎖的信號(hào)。接收到脈沖后,等待線(xiàn)程就被移動(dòng)到就緒隊(duì)列中。在調(diào)用?Pulse?的線(xiàn)程釋放鎖后,就緒隊(duì)列中的下一個(gè)線(xiàn)程(不一定是接收到脈沖的線(xiàn)程)將獲得該鎖。
另外:
? ? ? ??Wait 和 Pulse 方法必須寫(xiě)在?Monitor.Enter 和Moniter.Exit 之間。
上面是MSDN的解釋。不明白看代碼:
?首先我們定義一個(gè)攻擊類(lèi),
/// <summary>/// 怪物類(lèi)/// </summary>internal class Monster{public int Blood { get; set; }public Monster(int blood){this.Blood = blood;Console.WriteLine("我是怪物,我有{0}滴血",blood);}}然后在定義一個(gè)攻擊類(lèi)
/// <summary>/// 攻擊類(lèi)/// </summary>internal class Play{/// <summary>/// 攻擊者名字/// </summary>public string Name { get; set; } /// <summary>/// 攻擊力/// </summary>public int Power{ get; set; }/// <summary>/// 法術(shù)攻擊/// </summary>public void magicExecute(object monster){Monster m = monster as Monster;Monitor.Enter(monster);while (m.Blood>0){Monitor.Wait(monster);Console.WriteLine("當(dāng)前英雄:{0},正在使用法術(shù)攻擊打擊怪物", this.Name);if(m.Blood>= Power){m.Blood -= Power;}else{m.Blood = 0;}Thread.Sleep(300);Console.WriteLine("怪物的血量還剩下{0}", m.Blood);Monitor.PulseAll(monster);}Monitor.Exit(monster);}/// <summary>/// 物理攻擊/// </summary>/// <param name="monster"></param>public void physicsExecute(object monster){Monster m = monster as Monster;Monitor.Enter(monster);while (m.Blood > 0){Monitor.PulseAll(monster);if (Monitor.Wait(monster, 1000)) //非常關(guān)鍵的一句代碼{Console.WriteLine("當(dāng)前英雄:{0},正在使用物理攻擊打擊怪物", this.Name);if (m.Blood >= Power){m.Blood -= Power;}else{m.Blood = 0;}Thread.Sleep(300);Console.WriteLine("怪物的血量還剩下{0}", m.Blood);}}Monitor.Exit(monster);}}執(zhí)行代碼:
static void Main(string[] args){//怪物類(lèi)Monster monster = new Monster(1000);//物理攻擊類(lèi)Play play1 = new Play() { Name = "無(wú)敵劍圣", Power = 100 };//魔法攻擊類(lèi)Play play2 = new Play() { Name = "流浪法師", Power = 120 };Thread thread_first = new Thread(play1.physicsExecute); //物理攻擊線(xiàn)程Thread thread_second = new Thread(play2.magicExecute); //魔法攻擊線(xiàn)程thread_first.Start(monster);thread_second.Start(monster);Console.ReadKey();}輸出結(jié)果:
總結(jié):
第一種情況:
?第二種情況:thread_second首先獲得同步鎖對(duì)象,首先執(zhí)行到Monitor.PulseAll(monster),因?yàn)槌绦蛑袥](méi)有需要等待信號(hào)進(jìn)入就緒狀態(tài)的線(xiàn)程,所以這一句代碼沒(méi)有意義,當(dāng)執(zhí)行到?Monitor.Wait(monster, 1000),自動(dòng)將自己流放到等待隊(duì)列并在這里阻塞,1S 時(shí)間過(guò)后thread_second自動(dòng)添加到就緒隊(duì)列,線(xiàn)程thread_first獲得monster對(duì)象鎖,執(zhí)行到Monitor.Wait(monster);時(shí)發(fā)生阻塞釋放同步對(duì)象鎖,線(xiàn)程thread_second執(zhí)行,執(zhí)行Monitor.PulseAll(monster)時(shí)通知thread_first。于是又開(kāi)始第一種情況...
Monitor.Wait是讓當(dāng)前進(jìn)程睡眠在臨界資源上并釋放獨(dú)占鎖,它只是等待,并不退出,當(dāng)?shù)却Y(jié)束,就要繼續(xù)執(zhí)行剩下的代碼。
?
3.0 使用Mutex類(lèi)實(shí)現(xiàn)線(xiàn)程同步
? ? ??Mutex的突出特點(diǎn)是可以跨應(yīng)用程序域邊界對(duì)資源進(jìn)行獨(dú)占訪(fǎng)問(wèn),即可以用于同步不同進(jìn)程中的線(xiàn)程,這種功能當(dāng)然這是以犧牲更多的系統(tǒng)資源為代價(jià)的。
主要常用的兩個(gè)方法:
?public virtual bool WaitOne() ??阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)獲取互斥鎖。
?public void ReleaseMutex() ? ??釋放 System.Threading.Mutex 一次。
使用實(shí)例:
static void Main(string[] args){Thread[] thread = new Thread[3];for (int i = 0; i < 3; i++){thread[i] = new Thread(ThreadMethod1);thread[i].Name = i.ToString();}for (int i = 0; i < 3; i++){thread[i].Start();}Console.ReadKey(); } public static void ThreadMethod1(object val){mutet.WaitOne(); //獲取鎖for (int i = 0; i < 500; i++){Console.Write(Thread.CurrentThread.Name); } mutet.ReleaseMutex(); //釋放鎖}?2、線(xiàn)程池
? ? ??上面介紹了介紹了平時(shí)用到的大多數(shù)的多線(xiàn)程的例子,但在實(shí)際開(kāi)發(fā)中使用的線(xiàn)程往往是大量的和更為復(fù)雜的,這時(shí),每次都創(chuàng)建線(xiàn)程、啟動(dòng)線(xiàn)程。從性能上來(lái)講,這樣做并不理想(因?yàn)槊渴褂靡粋€(gè)線(xiàn)程就要?jiǎng)?chuàng)建一個(gè),需要占用系統(tǒng)開(kāi)銷(xiāo));從操作上來(lái)講,每次都要啟動(dòng),比較麻煩。為此引入的線(xiàn)程池的概念。
??好處:
? 1.減少在創(chuàng)建和銷(xiāo)毀線(xiàn)程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷(xiāo)?
? 2.如不使用線(xiàn)程池,有可能造成系統(tǒng)創(chuàng)建大量線(xiàn)程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及”過(guò)度切換”。
在什么情況下使用線(xiàn)程池??
??? 1.單個(gè)任務(wù)處理的時(shí)間比較短?
??? 2.需要處理的任務(wù)的數(shù)量大?
線(xiàn)程池最多管理線(xiàn)程數(shù)量=“處理器數(shù) * 250”。也就是說(shuō),如果您的機(jī)器為2個(gè)2核CPU,那么CLR線(xiàn)程池的容量默認(rèn)上限便是1000
通過(guò)線(xiàn)程池創(chuàng)建的線(xiàn)程默認(rèn)為后臺(tái)線(xiàn)程,優(yōu)先級(jí)默認(rèn)為Normal。
代碼示例:
static void Main(string[] args){ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod1), new object()); //參數(shù)可選Console.ReadKey();}public static void ThreadMethod1(object val){ for (int i = 0; i <= 500000000; i++){if (i % 1000000 == 0){Console.Write(Thread.CurrentThread.Name);} } }?
?
有關(guān)線(xiàn)程池的解釋請(qǐng)參考:
http://www.cnblogs.com/JeffreyZhao/archive/2009/07/22/thread-pool-1-the-goal-and-the-clr-thread-pool.html
總結(jié)
- 上一篇: 常见的投资品种有哪些?各类投资品种的对比
- 下一篇: C#中 ??、 ?、 ?: 、?.、?[