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);   //毫秒數

  實例代碼: 

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ThreadTest
{
    class Program
    {
        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("[{2}] 我是:{0},我循環{1}次", Thread.CurrentThread.Name, i, DateTime.Now.ToString("HH:mm:ss.fff"));
                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線程則同時執行了。

那么我們把代碼中的  ThreadA.join()方法和ThreadB.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類型的。見下圖:

直接上代碼來看效果:

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

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);
                }
            } 
        }
View Code

執行結果:

我們通過添加了 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);
                }
            }
        }
View Code

執行結果:

我們會發現,線程又沒有實現同步了!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);
                }
            }
        }
View Code

通過查看執行結果。會發現代碼實現了我們的需求,兩個線程按順序執行了。那么 lock(this) 和lock(Obj)有什么區別呢? 我們再看一個示例代碼:

static void Main(string[] args)
        {
            Class1 pro1 = new Class1();
            Class1 pro2 = new Class1();
            Thread threadA = new Thread(pro1.ThreadMethod); //執行的必須是無返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro2.ThreadMethod); //執行的必須是無返回值的方法 
            threadB.Name = "生旭鵬";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }

//另外新建一個類
 public class Class1
    {

        private static object obj = new object();

        public void ThreadMethod(object parameter)
        {
            lock (obj)   // 也可以使用 lock (typeof(Class1)) 方法來鎖定
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循環{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            }
        }
    }

 

復制代碼
lock(this) 鎖定 當前實例對象,如果有多個類實例的話,lock鎖定的只是當前類實例,對其它類實例無影響。所有不推薦使用。 
lock(typeof(Model))鎖定的是model類的所有實例。 這里的Model是指某個類名。
lock(obj)鎖定的對象是全局的私有化靜態變量。外部無法對該變量進行訪問。 
lock 確保當一個線程位於代碼的臨界區時,另一個線程不進入臨界區。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。 
所以,lock的結果好不好,還是關鍵看鎖的誰,如果外邊能對這個誰進行修改,lock就失去了作用。
所以,一般情況下,使用私有的、靜態的並且是只讀的對象。
復制代碼

總結:

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

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

3、保證lock的對象在外部無法修改才有意義,如果lock的對象在外部改變了,對其他線程就會暢通無阻,失去了lock的意義。

     不能鎖定字符串,鎖定字符串尤其危險,因為字符串被公共語言運行庫 (CLR)“暫留”。 這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。通常,最好避免鎖定 public 類型或鎖定不受應用程序lock塊內控制的對象實例。例如,如果該實例可以被公開訪問,則 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

 

======


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM