第二节:深入剖析Thread的五大方法、数据槽、内存栅栏
一. Thread及其五大方法
? Thread是.Net最早的多線程處理方式,它出現在.Net1.0時代,雖然現在已逐漸被微軟所拋棄,微軟強烈推薦使用Task(后面章節介紹),但從多線程完整性的角度上來說,我們有必要了解下N年前多線程的是怎么處理的,以便體會.Net體系中多線程處理方式的進化。
Thread中有五大方法,分別是:Start、Suspend、Resume、Intterupt、Abort
①.Start:開啟線程
②.Suspend:暫停線程
③.Resume:恢復暫停的線程
④.Intterupt:中斷線程(會拋異常,提示線程中斷)
⑤.Abort:銷毀線程
? ?這五大方法使用起來,也比較簡單,下面貼一段代碼,體會一下如何使用即可。
?
在這里補充一下,在該系列中,很多測試代碼中看到TestThread0、TestThread、TestThread2,分別對應無參、一個參數、兩個參數的耗時方法,代碼如下:
?View Code
?
二. 從源碼角度分析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循環來實現
?下面貼一下這三塊設計到的代碼:
?
?View Code
(四). 擴展: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 }?
?
?
總結
以上是生活随笔為你收集整理的第二节:深入剖析Thread的五大方法、数据槽、内存栅栏的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在 VS 类库项目中 Add Servi
- 下一篇: 过去40年时间,美国GDP总量增加了7倍