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。