今年第一次旅行結束,雖然是第二次進去藏區,依舊有高原反應,嚓....不過整個旅途感受到前所未有的放鬆.更有精力面對接下來的工作和學習.進入今天的主題---C#中的多線程
1、感受多線程
因為文章追求是簡單易懂,如果您和我一樣,是C#初學者,那麼在這一段中,請跟著例子,寫一次,每個例子都有分析,並且這裡的例子是會把多線程涉及的很多問題先引入出來,在後面的階段,再深入分析~
C#是支援多線程滴(貌似是廢話.)~一個線程有它獨立的執行路徑,能夠與其他的線程“同時”運行,一個C#程式起始於一個單線程,這個線程是被CLR和作業系統自動建立滴。~囉嗦了這麼多了,還是用一段簡單的代碼加以說明
第一個多線程程式
class Program { static void Main(string[] args) { Thread t = new Thread(WriteYes); t.Start(); while (true) { Console.Write("No"); } } static void WriteYes() { while (true) { Console.Write("Yes"); } } }
程式運行
程式分析:在主線程中建立了一個新的線程"t",完成的任務是重複的輸出"yes",同時主線程重複的列印"No",在這裡並沒有控制線程的執行,所以看到的運行結果是隨機的。
首先,先看一段書本上的:CLR分配每個線程到自己的記憶體堆棧上,來保證局部變數的分離運行
多線程調用同一個方法,方法中局部變數執行問題
static void Main(string[] args) { new Thread(Go).Start(); Go(); } static void Go() { for (int cycles = 0; cycles < 5; cycles++) { Console.Write("??"); } }
程式分析:在此程式中,聲明了一個方法Go,在Go這個方法中聲明並使用了一個局部變數'cycles',在主程式中,又開啟了一個線程(匿名線程)調用方法Go,和在主線程直接調用Go~通過運行結果可以看出,一共輸出了10個"?"
由此,可見變數'cycles'分別在自己的記憶體堆棧中建立,互不影響!那是不是所有的情況都是如此的呢?答案顯然是否定的
線程中引用共用的執行個體,共用資料問題,還是先看代碼,通過代碼來分析
多線程共用執行個體
#region 多線程共用執行個體 bool done; static void Main(string[] args) { Program pp = new Program();//建立了一個Program的執行個體 new Thread(pp.Go).Start(); pp.Go(); } //這裡和上面的“線程間局部變數”中的Go方法不一定,這裡是一個執行個體的方法, //也就是需要new Program一個執行個體才可以調用 void Go() { if (!done) { done = true; Console.WriteLine("done"); } } #endregion
程式分析:在主程式中,聲明了一個Program執行個體pp,在相同的Program執行個體中,兩個線程都調用了方法Go,此時共用了done欄位,所以程式運行只輸出一個"done",而不是兩個
靜態欄位在多線程中的影響
多線程中的靜態欄位
#region 多線程中的靜態欄位 static bool done; static void Main(string[] args) { new Thread(Go).Start(); Go(); } static void Go() { if (!done) { done = true; Console.WriteLine("done"); } } #endregion
程式運行結果:
看到這裡,也許你會想,靜態欄位和執行個體欄位在多線程中是否沒有任何影響,稍等,我們將上面的代碼進行簡單的修改,將Go方法修改如下
修改後的Go方法
static void Go() { if (!done) { Console.WriteLine("done"); done = true; } }
在多次執行這個程式,輸出done的次數在1和2次之間沒規律的切換~
程式分析:從啟動並執行結果上看,這個輸出的次數就不受我們程式的控制,問題就是在一個線程在執行if判斷的時候,恰好此時另一個線程正在執行輸出語句WriteLine語句.當這個線程執行完WriteLine語句,在將done設定為true之前,執行if判斷的線程發現done還是為false,所以也會執行WriteLine語句.導致了問題的產生----安全執行緒問題
安全執行緒問題
那麼如何解決上面遇到的問題呢?拋開程式來說,我們兩個人去做一件事,那麼為了避免重複,我們可以這樣,先獲得做事許可權的人,先設定一個標誌,告訴後面來的人,我現在正在執行任務,請不要進入現場。程式中也是這樣的原理,在C#中提供了lock語句來實現(鎖的機制)
安全執行緒
#region 安全執行緒 static bool done; static object locker = new object(); static void Main(string[] args) { new Thread(Go).Start(); Go(); Console.Read(); } static void Go() { lock (locker) { if (!done) { Console.WriteLine("done"); done = true; } } } #endregion
我們再試著多次運行程式,會發現只會輸出一個done,當兩個線程在爭奪一個鎖的時候(例子中的是locker),一個線程會處於等待或者說是阻止狀態知道那個鎖變成可用,確保在同一時刻只有一個線程進入了工作區,所以保證只輸出了一次done.
在完成上面幾個簡單的例子之後,我們應該思考,在何時使用多線程~
何時使用多線程
其實這個話題,我想是仁者見仁智者見智了,但核心的思想都是一致的,多線程程式一般被用來在後台處於耗時的任務,主線程保持運行,並且背景工作執行緒在後台默默無聞的工作,對於Winform程式來說,如果主線程(UI線程)執行一段耗時操作的代碼,鍵盤和滑鼠的操作會變得很遲鈍,時間稍微長點,就整個程式就失去了響應。