C#多線程和線程池[轉]

一、概念html

  1.0 線程的和進程的關係以及優缺點數據庫

  windows系統是一個多線程的操做系統。一個程序至少有一個進程,一個進程至少有一個線程。進程是線程的容器,一個C#客戶端程序開始於一個單獨的線程,CLR(公共語言運行庫)爲該進程建立了一個線程,該線程稱爲主線程。例如當咱們建立一個C#控制檯程序,程序的入口是Main()函數,Main()函數是始於一個主線程的。它的功能主要 是產生新的線程和執行程序。C#是一門支持多線程的編程語言,經過Thread類建立子線程,引入using System.Threading命名空間。 編程

多線程的優勢 windows

1
2
一、 多線程能夠提升CPU的利用率,由於當一個線程處於等待狀態的時候,CPU會去執行另外的線程
二、 提升了CPU的利用率,就能夠直接提升程序的總體執行速度

多線程的缺點:安全

 

1
2
3
一、線程開的越多,內存佔用越大
二、協調和管理代碼的難度加大,須要CPU時間跟蹤線程
三、線程之間對資源的共享可能會產生可不遇知的問題

 

     1.1 前臺線程和後臺線程服務器

     C#中的線程分爲前臺線程和後臺線程,線程建立時不作設置默認是前臺線程。即線程屬性IsBackground=false。多線程

Thread.IsBackground = false;//false:設置爲前臺線程,系統默認爲前臺線程。

 區別以及如何使用:編程語言

    這二者的區別就是:應用程序必須運行完全部的前臺線程才能夠退出;而對於後臺線程,應用程序則能夠不考慮其是否已經運行完畢而直接退出,全部的後臺線程在應用程序退出時都會自動結束。通常後臺線程用於處理時間較短的任務,如在一個Web服務器中能夠利用後臺線程來處理客戶端發過來的請求信息。而前臺線程通常用於處理須要長時間等待的任務,如在Web服務器中的監聽客戶端請求的程序。ide

線程是寄託在進程上的,進程都結束了,線程也就不復存在了!函數

只要有一個前臺線程未退出,進程就不會終止!即說的就是程序不會關閉!(即在資源管理器中能夠看到進程未結束。)

     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.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接着執行,最後纔是主線程執行。

看執行結果:

blob.png

        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類型的。見下圖:

直接上代碼來看效果:

複製代碼
static void Main(string[] args)
        {                
            Thread threadA = new Thread(ThreadMethod); //執行的必須是無返回值的方法 
            threadA.Name = "A";
            Thread threadB = new Thread(ThreadMethod); //執行的必須是無返回值的方法 
            threadB.Name = "B";
            threadA.Priority = ThreadPriority.Highest;
            threadB.Priority = ThreadPriority.BelowNormal;
            threadB.Start();
            threadA.Start();
            Thread.CurrentThread.Name = "C";
            ThreadMethod(new object());
            Console.ReadKey();
        }
        public static void ThreadMethod(object parameter)
        {
            for (int i = 0; i < 500; i++)
            { 
                Console.Write(Thread.CurrentThread.Name); 
            }
        }
複製代碼

執行結果:

上面的代碼中有三個線程,threadA,threadB和主線程,threadA優先級最高,threadB優先級最低。這一點從運行結果中也能夠看出,線程B 偶爾會出如今主線程和線程A前面。當有多個線程同時處於可執行狀態,系統優先執行優先級較高的線程,但這隻意味着優先級較高的線程佔有更多的CPU時間,並不意味着必定要先執行完優先級較高的線程,纔會執行優先級較低的線程。

優先級越高表示CPU分配給該線程的時間片越多,執行時間就多

優先級越低表示CPU分配給該線程的時間片越少,執行時間就少

   三、線程同步

  什麼是線程安全:

  線程安全是指在當一個線程訪問該類的某個數據時,進行保護,其餘線程不能進行訪問直到該線程讀取完,其餘線程纔可以使用。不會出現數據不一致或者數據污染。

   線程有可能和其餘線程共享一些資源,好比,內存,文件,數據庫等。當多個線程同時讀寫同一份共享資源的時候,可能會引發衝突。這時候,咱們須要引入線程「同步」機制,即各位線程之間要有個先來後到,不能一窩蜂擠上去搶做一團。線程同步的真實意思和字面意思剛好相反。線程同步的真實意思,實際上是「排隊」:幾個線程之間要排隊,一個一個對共享資源進行操做,而不是同時進行操做。

爲何要實現同步呢,下面的例子咱們拿著名的單例模式來講吧。看代碼

複製代碼
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這個方法,這顯然不符合咱們線程同步的要求。咱們對代碼進行修改以下:

複製代碼
static void Main(string[] args)
        {
            Program pro = new Program();
            Thread threadA = new Thread(pro.ThreadMethod); //執行的必須是無返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro.ThreadMethod); //執行的必須是無返回值的方法 
            threadB.Name = "生旭鵬";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (this)             //添加lock關鍵字
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循環{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            } 
        }
複製代碼

執行結果:

咱們經過添加了 lock(this) {...}代碼,查看執行結果實現了咱們想要的線程同步需求。可是咱們知道this表示當前類實例的自己,那麼有這麼一種狀況,咱們把須要訪問的方法所在的類型進行兩個實例A和B,線程A訪問實例A的方法ThreadMethod,線程B訪問實例B的方法ThreadMethod,這樣的話還可以達到線程同步的需求嗎。

複製代碼
static void Main(string[] args)
        {
            Program pro1 = new Program();                    
            Program pro2 = new Program();                   
            Thread threadA = new Thread(pro1.ThreadMethod); //執行的必須是無返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro2.ThreadMethod); //執行的必須是無返回值的方法 
            threadB.Name = "生旭鵬";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (this)
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循環{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            }
        }
複製代碼

執行結果:

咱們會發現,線程又沒有實現同步了!lock(this)對於這種狀況是不行的!因此須要咱們對代碼進行修改!修改後的代碼以下: 

複製代碼
private static object obj = new object();
        static void Main(string[] args)
        {
            Program pro1 = new Program();                    
            Program pro2 = new Program();                   
            Thread threadA = new Thread(pro1.ThreadMethod); //執行的必須是無返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro2.ThreadMethod); //執行的必須是無返回值的方法 
            threadB.Name = "生旭鵬";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (obj)
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循環{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            }
        }
複製代碼

經過查看執行結果。會發現代碼實現了咱們的需求。那麼 lock(this) 和lock(Obj)有什麼區別呢? 

lock(this) 鎖定 當前實例對象,若是有多個類實例的話,lock鎖定的只是當前類實例,對其它類實例無影響。全部不推薦使用。 
lock(typeof(Model))鎖定的是model類的全部實例。 
lock(obj)鎖定的對象是全局的私有化靜態變量。外部沒法對該變量進行訪問。 
lock 確保當一個線程位於代碼的臨界區時,另外一個線程不進入臨界區。若是其餘線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。 
因此,lock的結果好很差,仍是關鍵看鎖的誰,若是外邊能對這個誰進行修改,lock就失去了做用。因此通常狀況下,使用私有的、靜態的而且是隻讀的對象。

總結:

一、lock的是必須是引用類型的對象,string類型除外。

二、lock推薦的作法是使用靜態的、只讀的、私有的對象。

三、保證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();
        }
複製代碼

輸出結果:

總結:

  第一種狀況:

  1. thread_first首先得到同步對象的鎖,當執行到 Monitor.Wait(monster);時,thread_first線程釋放本身對同步對象的鎖,流放本身到等待隊列,直到本身再次得到鎖,不然一直阻塞。
  2. 而thread_second線程一開始就競爭同步鎖因此處於就緒隊列中,這時候thread_second直接從就緒隊列出來得到了monster對象鎖,開始執行到Monitor.PulseAll(monster)時,發送了個Pulse信號。
  3. 這時候thread_first接收到信號進入到就緒狀態。而後thread_second繼續往下執行到 Monitor.Wait(monster, 1000)時,這是一句很是關鍵的代碼,thread_second將本身流放到等待隊列並釋放自身對同步鎖的獨佔,該等待設置了1S的超時值,當B線程在1S以內沒有再次獲取到鎖自動添加到就緒隊列。
  4. 這時thread_first從Monitor.Wait(monster)的阻塞結束,返回true。開始執行、打印。執行下一行的Monitor.Pulse(monster),這時候thread_second假如1S的時間還沒過,thread_second接收到信號,因而將本身添加到就緒隊列。
  5. thread_first的同步代碼塊結束之後,thread_second再次得到執行權, Monitor.Wait(m_smplQueue, 1000)返回true,因而繼續從該代碼處往下執行、打印。當再次執行到Monitor.Wait(monster, 1000),又開始了步驟3。
  6. 依次循環。。。。

   第二種狀況: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();  //釋放鎖
        }
複製代碼

 二、線程池

      上面介紹了介紹了平時用到的大多數的多線程的例子,但在實際開發中使用的線程每每是大量的和更爲複雜的,這時,每次都建立線程、啓動線程。從性能上來說,這樣作並不理想(由於每使用一個線程就要建立一個,須要佔用系統開銷);從操做上來說,每次都要啓動,比較麻煩。爲此引入的線程池的概念。

  好處:

  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

相關文章
相關標籤/搜索