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

来源:https://www.cnblogs.com/slcands/archive/2018/01/07/8227375.html
-Advertisement-
Play Games

1、概念 1.0 線程的和進程的關係以及優缺點 windows系統是一個多線程的操作系統。一個程式至少有一個進程,一個進程至少有一個線程。進程是線程的容器,一個C#客戶端程式開始於一個單獨的線程,CLR(公共語言運行庫)為該進程創建了一個線程,該線程稱為主線程。例如當我們創建一個C#控制台程式,程式 ...


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接著執行,最後才是主線程執行。

看執行結果:

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分配給該線程的時間片越少,執行時間就少

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

複製代碼
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就失去了作用。所以一般情況下,使用私有的、靜態的並且是只讀的對象。

總結:

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

輸出結果:

總結:

  第一種情況:

  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();  //釋放鎖
        }
複製代碼

 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先是打開Content Assistant,自動代碼補全 Window Preferences Java Editor Content Assist,在最下麵的Auto Activation區域的Auto activation triggers for java裡面把26個英文字母都敲一遍 CTR ...
  • 定時任務總會遇到任務重疊執行的情況,比如一個任務1分鐘執行一次,而任務的執行時間超過了1分鐘,這樣就會有兩個相同任務併發執行了。有時候我們是允許這種情況的發生的,比如任務執行的代碼是冪等的,而有時候我們可能考慮到一些情況是不允許這種事情發生的。 在實際場景中,我們定時任務調度使用quartz來實現觸 ...
  • 凱魯嘎吉 - 博客園 http://www.cnblogs.com/kailugaji/ 某糖果廠用原料A、B和C按不向比率混合加工而成甲、乙、丙三種糖果(假設混合加工中不損耗原料)。原料A、B、C在糖果甲、乙、丙中的含量、原料成本、加工成本、原料限量及糖果售價如表所示。 問該廠對這三種糖果各生產多 ...
  • 利用Java 8中新引入的LocalDate類來計算時間間隔,本文將用一段極其簡單的代碼來示範如何計算兩個日期之間間隔的年數。 ...
  • 凱魯嘎吉 - 博客園 http://www.cnblogs.com/kailugaji/ 說明: Lingo版本: 某工廠明年根據合同,每個季度末向銷售公司提供產品,有關信息如下表。若當季生產的產品過多,季末有積餘,則一個季度每積壓一噸產品需支付存貯費O.2萬元。現該廠考慮明年的最佳生產方案,使該廠 ...
  • 微信跳一跳輔助工具 準備工具 adb驅動 安卓手機 打開手機的調試模式 usb接好手機和電腦 PyCharm:全宇宙唯一一款專門用於Python開發IDE工具 實現原理: 獲取手機的實時的截圖 點擊起始位置和落地位置 技算兩個點的距離 計算按壓時間 發送按壓指令 重新刷新手機截圖 ...
  • 一:裝飾器 1 函數對象有一個__name__屬性,可以拿到函數的名字 上面的log,因為它是一個decorator,所以接受一個函數作為參數,並返回一個函數。我們要藉助Python的@語法,把decorator置於函數的定義處: 調用now()函數,不僅會運行now()函數本身,還會在運行now( ...
  • 一. 虛擬環境搭建 在開發中安裝模塊的方法: pip install 模塊名稱 之前我們安裝模塊都是直接在物理環境下安裝,這種安裝方法,後面一次安裝的會覆蓋掉前面一次安裝的。那如果一臺機器上面開發多個項目使用到不同版本的模塊呢?怎麼樣做才能不受版本影響!那麼需要用到虛擬環境,每個虛擬環境互相隔離,在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...