1. 程式人生 > >C#多執行緒基礎知識和小實踐

C#多執行緒基礎知識和小實踐

源文來源 :http://www.cnblogs.com/jackson0714/p/5100372.html

一、多執行緒介紹

C#通過多執行緒支援並行執行的程式碼。一個執行緒是一個獨立執行的路徑,可以同時與其他執行緒一起執行。一個C#客戶端程式(Console,WPF,Winows
Forms)開始於一個單獨的執行緒,該執行緒由CLR和作業系統自動地建立,我們稱它為主執行緒,而且可以通過建立附加的執行緒來實現多執行緒。

1.一個簡單的栗子

class Program
 2 {
 3             static void Main(string[] args)
 4             {
 5                 Thread thread = new Thread(WriteY);//建立一個執行緒
 6                 thread.Start();//開始一個執行緒
 7     
 8                 for (int i = 0; i < 1000; i++)//主執行緒執行迴圈
 9                 {
10                     Console.Write("x");
11                 }
12     
13                 Console.ReadLine();
14             }
15             static void WriteY()
16             {
17                 for (int i = 0; i < 1000; i++)
18                 { 
19                     Console.Write("y"); 
20                 }
21             }
22 
23     }

在這裡插入圖片描述
一旦開始,一個執行緒的IsAlive屬性返回true,直到這個執行緒結束。當傳遞給執行緒的建構函式的委託完成執行時,這個執行緒結束。一旦結束,這個執行緒不能重啟。IsAlive:判斷執行緒是否還活著

2.記憶體隔離

CLR給每個執行緒分配自己記憶體棧,因此區域性變數可以保持分離。在下面例子中,我們定義了一個使用區域性變數的方法,然後在主執行緒和子執行緒同時呼叫這個方法

class Program
{
    static void Main(string[] args)
    {
        new Thread(Go).Start();
        Go();
        Console.ReadKey();
    }

    static void Go()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.Write("y");
        }
    }
}

執行結果:
在這裡插入圖片描述

3.資料共享

如果多個執行緒對同一個物件例項有相同的引用,這些執行緒就共享這個物件例項的資料。

class ThreadTest 
{
  static bool done;    // 靜態欄位在所有執行緒之間共享
 
  static void Main()
  {
    new Thread (Go).Start();
    Go();
  }
 
  static void Go()
  {
    if (!done)
     { 
     done = true;
      Console.WriteLine ("Done"); 
     }
  }
}

在這裡插入圖片描述

4.執行緒安全

上面兩個例子展示了另外一個重要的概念:執行緒安全確實是不確定的:done可能被打印出兩次(儘管是不太可能發生的)。當我們把Go方法中的語句的順序交換下,打印出兩次done的機率顯著提升

class ThreadTest 
{
  static bool done;    // 靜態欄位在所有執行緒之間共享
 
  static void Main()
  {
    new Thread (Go).Start();
    Go();
  }
 
  static void Go()
  {
    if (!done)
     { 
       Console.WriteLine ("Done"); 
       done = true;
     }
  }
}

在這裡插入圖片描述
這個地方的問題是執行緒A線上程B設定done等於true之前進入if條件判斷中,所有A有機會打印出"Done"。

改進方式: 當讀\寫一個公共欄位時,獲取一個獨佔鎖(exclusive lock)。C#提供了關鍵字lock。

class Program
{
    static bool done = false;
    static readonly object locker = new object();
    static void Main(string[] args)
    {
        new Thread(Go).Start();
        Go();
        Console.ReadKey();
    }

    static void Go()
    {
        lock (locker)
        {
            if (!done)
            {
                Console.WriteLine("Done");
                done = true;
            }
        }
    }
}

在這裡插入圖片描述

當兩個執行緒同時搶佔一個鎖時(在這個例子中,locker),一個執行緒等待,或者阻塞,知道這個鎖釋放。在這個例子中,這個鎖保證一次只有一個執行緒可以進入程式碼的臨界區域,然後“Done”只會被列印一次。程式碼在這種不確定的多執行緒背景下中被保護被叫做執行緒安全。

注意:在多執行緒中,共享資料是造成複雜原因的主要,而且會產生讓人費解的錯誤。儘管很基本但還是要儘可能保持簡單。
一個執行緒,當阻塞的時候,不佔用CPU資源。

二、Join 和Sleep

1.Join

當程式執行時,啟動了一個耗時較長的執行緒來列印數字,列印每個數字前要等待兩秒。但我們在主程式中呼叫了t.Join方法,該方法允許我們等待直到執行緒t完成。當執行緒t完成 "時,主程式會繼續執行。藉助該技術可以實現在兩個執行緒間同步執行步驟。第一個執行緒會等待另一個執行緒完成後再繼續執行。第一個執行緒等待時是處於阻塞狀態(正如暫停執行緒中呼叫 Thread.Sleep方法一樣),。

 static void Main(string[] args)
        {
            Thread t = new Thread(Print);
            t.Start();
            t.Join();
            MainPrint();
        }
        static void MainPrint()
        {
            Console.WriteLine("主執行緒執行");
        }
        static void Print()
        {
            Console.WriteLine("執行緒執行");
        }

在這裡插入圖片描述
當使用了Join方法後,等待Print執行完畢後,才執行MainPrint

2.Sleep
Thread.Sleep暫停當前執行緒一段指定的時間:
Thread.Sleep(TimeSpan.FromHours(1));//sleep一個小時
Thread.Sleep(500);//sleep 500 微秒
當使用Sleep或Join暫停執行緒時,這個執行緒是阻塞的,不消耗CPU資源

 static void Main(string[] args)
        {
            Thread t = new Thread(Print);
            t.Start();
            Thread.Sleep(TimeSpan.FromSeconds(5));
            MainPrint();
        }
        static void MainPrint()
        {
            Console.WriteLine("主執行緒執行");
        }
        static void Print()
        {
            Console.WriteLine("執行緒執行");
        }

結果是Print先執行,5秒後,MainPrint才執行

Thread.Sleep(0)立即放棄這個執行緒的時間片,主動交出CPU給其他執行緒。Framework 4.0的新方法Thread.Yield()方法做同樣的事,除了當它僅僅在同一個程序中時,才會放棄時間片。
Sleep(0)或Yield()有時候對提升產品效能有用。而且它們也是診斷工具可以幫助揭開執行緒安全的問題; 如果在程式碼中的任何地方都插入Thread.Yield(),會造成bug。