一. Thread及其五大方法
Thread是.Net最早的多線程處理方式,它出現在.Net1.0時代,雖然現在已逐漸被微軟所拋棄,微軟強烈推薦使用Task(后面章節介紹),但從多線程完整性的角度上來說,我們有必要了解下N年前多線程的是怎么處理的,以便體會.Net體系中多線程處理方式的進化。
Thread中有五大方法,分別是:Start、Suspend、Resume、Intterupt、Abort
①.Start:開啟線程
②.Suspend:暫停線程
③.Resume:恢復暫停的線程
④.Intterupt:中斷線程(會拋異常,提示線程中斷)
⑤.Abort:銷毀線程
這五大方法使用起來,也比較簡單,下面貼一段代碼,體會一下如何使用即可。
在這里補充一下,在該系列中,很多測試代碼中看到TestThread0、TestThread、TestThread2,分別對應無參、一個參數、兩個參數的耗時方法,代碼如下:

1 /// <summary> 2 /// 執行動作:耗時而已 3 /// </summary> 4 private void TestThread0() 5 { 6 Console.WriteLine("線程開始:當前線程的id為:{0},當前時間為:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")); 7 long sum = 0; 8 for (int i = 1; i < 999999999; i++) 9 { 10 sum += i; 11 } 12 Console.WriteLine("線程結束:當前線程的id為::{0},當前時間為:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")); 13 } 14 15 /// <summary> 16 /// 執行動作:耗時而已 17 /// </summary> 18 private void TestThread(string threadName) 19 { 20 Console.WriteLine("線程開始:線程名為:{2},當前線程的id為:{0},當前時間為:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName); 21 long sum = 0; 22 for (int i = 1; i < 999999999; i++) 23 { 24 sum += i; 25 } 26 Console.WriteLine("線程結束:線程名為:{2},當前線程的id為::{0},當前時間為:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName); 27 } 28 29 /// <summary> 30 /// 執行動作:耗時而已 31 /// </summary> 32 private void TestThread2(string threadName1, string threadName2) 33 { 34 Console.WriteLine("線程開始:線程名為:{2}和{3},當前線程的id為:{0},當前時間為:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2); 35 long sum = 0; 36 for (int i = 1; i < 999999999; i++) 37 { 38 sum += i; 39 } 40 Console.WriteLine("線程結束:線程名為:{2}和{3},當前線程的id為::{0},當前時間為:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2); 41 }
二. 從源碼角度分析Thread類
(1) 分析Thread類的源碼,發現其構造函數有兩類,分別是ThreadStart和ParameterizedThreadStart類,
其中
①:ThreadStart類,是無參無返回值的委托。
②:ParameterizedThreadStart類,是有一個object類型參數但無返回值的委托.
使用方法:
①:針對ThreadStart類, ThreadStart myTs = () => TestThread(name); 然后再把myTs傳入Thread的構造函數中
②:針對ParameterizedThreadStart類,ParameterizedThreadStart myTs = o => this.TestThread(o.ToString()); 然后再把myTs傳入Thread的構造函數中
注:該方式存在拆箱和裝箱的轉換問題,不建議這么使用
通用寫法:
Thread t = new Thread(() =>
{
Console.Write("333");
});
t.Start();
無須考慮Thread的構造函數,也不需要考慮Start的時候傳參,直接使用()=>{}的形式,解決一切問題。
(二) 前台進程和后台進程(IsBackground屬性)
①:前台進程,Thread默認為前台線程,程序關閉后,線程仍然繼續,直到計算完為止
②:后台進程,將IsBackground屬性設置為true,即為后台進程,主線程關閉,所有子線程無論運行完否,都馬上關閉
(三) 線程等待(Join方法)
利用Join方法實現主線程等待子線程,當多個子線程進行等待的時候,只能通過for循環來實現
下面貼一下這三塊設計到的代碼:

1 private void button3_Click(object sender, EventArgs e) 2 { 3 Stopwatch watch = new Stopwatch(); 4 watch.Start(); 5 Console.WriteLine("-----------------Thread多線程 --------------------------"); 6 Console.WriteLine("----------------- button_Click 開始 主線程id為:{0} --------------------------", Thread.CurrentThread.ManagedThreadId); 7 List<Thread> threadList = new List<Thread>(); 8 for (int i = 0; i < 5; i++) 9 { 10 string name = string.Format("button1_Click{0}", i); 11 12 #region 方式一 13 { 14 ThreadStart myTs = () => TestThread(name); 15 Thread myThread = new Thread(myTs); 16 //Thread默認為前台線程,程序關閉后,線程仍然繼續,直到計算完為止 17 myThread.IsBackground = true; //設置為后台線程,主程序關閉所有線程均關閉 18 myThread.Start(); 19 20 threadList.Add(myThread); 21 } 22 23 #endregion 24 25 #region 方式二 26 //{ 27 // ParameterizedThreadStart myTs = o => this.TestThread(o.ToString()); 28 // //ParameterizedThreadStart myTs = (o) => 29 // //{ 30 // // this.TestThread(o.ToString()); 31 // //}; 32 // Thread myThread = new Thread(myTs); 33 // myThread.Start(name); 34 35 // threadList.Add(myThread); 36 //} 37 38 #endregion 39 } 40 41 #region Thread線程等待 42 43 //利用join方法進行線程等待 44 foreach (Thread thread in threadList) 45 { 46 thread.Join(); 47 } 48 #endregion 49 50 watch.Stop(); 51 Console.WriteLine("----------------- button_Click 結束 主線程id為:{0} 總耗時:{1}--------------------------", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds); 52 53 }
(四). 擴展:Thread實現線程回調
三. 數據槽-線程可見性
背景:為了解決多線程競用共享資源的問題,引入數據槽的概念,即將數據存放到線程的環境塊中,使該數據只能單一線程訪問.(屬於線程空間上的開銷)
下面的三種方式是解決多線程競用共享資源的通用方式:
①:AllocateNamedDataSlot命名槽位和AllocateDataSlot未命名槽位 解決線程競用資源共享問題。
(PS:在主線程上設置槽位,使該數據只能被主線程讀取,其它線程無法訪問)
private void button10_Click(object sender, EventArgs e) { #region 01-AllocateNamedDataSlot命名槽位 { var d = Thread.AllocateNamedDataSlot("userName"); //在主線程上設置槽位,使該數據只能被主線程讀取,其它線程無法訪問 Thread.SetData(d, "ypf"); //聲明一個子線程 var t1 = new Thread(() => { Console.WriteLine("子線程中讀取數據:{0}", Thread.GetData(d)); }); t1.Start(); //主線程中讀取數據 Console.WriteLine("主線程中讀取數據:{0}", Thread.GetData(d)); } #endregion #region 02-AllocateDataSlot未命名槽位 { var d = Thread.AllocateDataSlot(); //在主線程上設置槽位,使該數據只能被主線程讀取,其它線程無法訪問 Thread.SetData(d, "ypf"); //聲明一個子線程 var t1 = new Thread(() => { Console.WriteLine("子線程中讀取數據:{0}", Thread.GetData(d)); }); t1.Start(); //主線程中讀取數據 Console.WriteLine("主線程中讀取數據:{0}", Thread.GetData(d)); } #endregion }
②:利用特性[ThreadStatic] 解決線程競用資源共享問題
(PS:在主線程中給ThreadStatic特性標注的變量賦值,則只有主線程能訪問該變量)
③:利用ThreadLocal線程的本地存儲, 解決線程競用資源共享問題(線程可見性)
(PS: 在主線程中聲明ThreadLocal變量,並對其賦值,則只有主線程能訪問該變量)
四. 內存柵欄-線程共享資源
背景:當多個線程共享一個變量的時候,在Release模式的優化下,子線程會將共享變量加載的cup Cache中,導致主線程不能使用該變量而無法運行。
解決方案:
①:不要讓多線程去操作同一個共享變量,從根本上解決這個問題。
②:利用MemoryBarrier方法進行處理,在此方法之前的內存寫入都要及時從cpu cache中更新到 memory;在此方法之后的內存讀取都要從memory中讀取,而不是cpu cache。
③:利用VolatileRead/Write方法進行處理。

1 private void button11_Click(object sender, EventArgs e) 2 { 3 #region 01-默認情況(Release模式主線程不能正常運行) 4 //{ 5 // var isStop = false; 6 // var t = new Thread(() => 7 // { 8 // var isSuccess = false; 9 // while (!isStop) 10 // { 11 // isSuccess = !isSuccess; 12 // } 13 // Console.WriteLine("子線程執行成功"); 14 // }); 15 // t.Start(); 16 17 // Thread.Sleep(1000); 18 // isStop = true; 19 20 // t.Join(); 21 // Console.WriteLine("主線程執行結束"); 22 //} 23 #endregion 24 25 #region 02-MemoryBarrier解決共享變量(Release模式下可以正常運行) 26 //{ 27 // var isStop = false; 28 // var t = new Thread(() => 29 // { 30 // var isSuccess = false; 31 // while (!isStop) 32 // { 33 // Thread.MemoryBarrier(); 34 35 // isSuccess = !isSuccess; 36 // } 37 // Console.WriteLine("子線程執行成功"); 38 // }); 39 // t.Start(); 40 41 // Thread.Sleep(1000); 42 // isStop = true; 43 44 // t.Join(); 45 // Console.WriteLine("主線程執行結束"); 46 //} 47 #endregion 48 49 #region 03-VolatileRead解決共享變量(Release模式下可以正常運行) 50 { 51 var isStop = 0; 52 var t = new Thread(() => 53 { 54 var isSuccess = false; 55 while (isStop == 0) 56 { 57 Thread.VolatileRead(ref isStop); 58 59 isSuccess = !isSuccess; 60 } 61 Console.WriteLine("子線程執行成功"); 62 }); 63 t.Start(); 64 65 Thread.Sleep(1000); 66 isStop = 1; 67 68 t.Join(); 69 Console.WriteLine("主線程執行結束"); 70 } 71 #endregion 72 73 74 }