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