第二節:深入剖析Thread的五大方法、數據槽、內存柵欄。


一. 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         }
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循環來實現

 下面貼一下這三塊設計到的代碼:

 

 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         }
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         }
View Code

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM