C#中的線程之線程同步

寫在前面

之前寫過一篇博客,C#中的線程之Abort陷阱,最近比較忙,沒時間寫後面的內容,恰好今天偶然看到一個技術問答。線程搶佔執行怎麼使10-20連續輸出 。回答該問題順便寫了幾個例子,於是就有了這篇博客。

問題描述

主要解決的問題是讓兩個線程代碼能按照一定的順序執行。題主拋出的代碼是這樣的:
// 需要讓打印出來的結果順序輸出
 static void Main(string[] args)
        {

            Thread th = new Thread(() =>
                {
                    for (int i = 0; i < 10; i++) 
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(100);
                    }
                });
            Thread th2 = new Thread(() =>
            {
                th.Join();
                for (int i = 10; i < 20; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(100);
                }
            });
            th.Start(); 
            th2.Start(); 

        }

輸出:
10
0
1
11
12
2
13
3
14
4
15
5
16
6
17
7
8
18
9
19

問題分析

原代碼中開啓了兩個線程,一個順序輸出0-9,另一個順序輸出10-20,由於是兩個不同的線程,那麼原問題就可以轉換成兩個線程之間的順序執行。要實現這個功能,.NetFramework提供了很多的方法,我在此就拋磚引玉,給出幾種我比較熟悉的方法。

解決方案

方案一
在初始線程的位置等待線程完成。
即在線程一執行完畢後,再開啓線程二的執行。
th.Start();
th.Join();
th2.Start();
這種方法的優點是簡單,缺點是不易擴展,若線程1與線程2都需要同時執行某段代碼,或線程12的執行不希望影響主線程的執行。
所以我們一定還有更好的方法實現這樣的效果

方案二
在線程二執行時等待。
即在線程二開啓後,等待線程一的完成。代碼如下:

static void Main(string[] args)
        {

            Thread th = new Thread(() =>
                {
                    for (int i = 0; i < 10; i++) 
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(100);
                    }
                });
            Thread th2 = new Thread(() =>
            {
                th.Join();
                for (int i = 10; i < 20; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(100);
                }
            });
            th.Start(); 
            th2.Start();
            Console.WriteLine("線程啓動完畢!"); 

        }

此方法的優點正好彌補了方案一的缺點,但它的缺點是我們必須知道線程1的實例。這在實際項目中有可能就很麻煩了,
如果有10個這樣的線程需要進行同步執行。那10個線程都需要知道上一個線程的實例,並在自己的線程中等待。
這樣寫代碼是難以讀懂並維護的。就着代碼簡潔的原則,我們也需要考慮其它的實現方式。

方案三
使用鎖來實現。代碼如下:

 static void Main(string[] args)
        {
            object ThreadLocker =new  object();
            Thread th = new Thread(() =>
                {
                    lock (ThreadLocker)
                    {
                        for (int i = 0; i < 10; i++)
                        {
                            Console.WriteLine(i);
                            Thread.Sleep(100);
                        }
                    }
                });
            Thread th2 = new Thread(() =>
            {
                lock (ThreadLocker)
                {
                    for (int i = 10; i < 20; i++)
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(100);
                    }
                }
            });

            Thread th3 = new Thread(() =>
            {
                lock (ThreadLocker)
                {
                    for (int i = 20; i < 30; i++)
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(100);
                    }
                }
            });
            th.Start();
            while (th.ThreadState != ThreadState.Running)
            {
                //Console.WriteLine("等待線程1執行"); 
            }
            th2.Start();
            while (th2.ThreadState != ThreadState.Running)
            {
                //Console.WriteLine("等待線程2執行");
            }
            th3.Start();
            while (th3.ThreadState != ThreadState.Running)
            {
                //Console.WriteLine("等待線程3執行");
            }
            Console.WriteLine("線程啓動完畢!"); 

        }

線程鎖來決定對資源的互斥,注意代碼中在主線程中用while判斷線程的狀態是爲了達到線程順序啓動的目的(因爲在多核的情況下,線程的執行順序是不確定的)。
注意這裏開了三個線程,在while等待的時候我起初準備使用Console.WriteLine來打印確定時序,實際發現這樣並不能精準反映時序。因爲這裏涉及到Console.WriteLine的線程安全問題,
此問題不在本題討論範圍內,可以考慮使用System.Diagnostics.Debug.WriteLine在調試狀態下查看輸出窗口。
還有個要注意的是: 主線程的執行還是會阻塞!雖然只有很少的時間,若主線程少量阻塞不影響程序的功能,那這種方法是可行的,但若對要求較高,那我們還得考慮其它方法。

方案四
使用信號量,信號量也是常用的線程同步方案之一。

 static void Main(string[] args)
        {
            int iThreadNum = 3;
            AutoResetEvent[] evts = new AutoResetEvent[iThreadNum];
            for (int i = 0; i < iThreadNum; i++)
            {
                evts[i] = new AutoResetEvent(false);
            } 
            Thread th = new Thread(() =>
             {  
                 for (int i = 0; i < 10; i++)
                 {
                     Console.WriteLine(i);
                     Thread.Sleep(100);
                 }
                 evts[0].Set();
             });
            Thread th2 = new Thread(() =>
            { 
                evts[0].WaitOne();  
                for (int i = 10; i < 20; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(100);
                }
                evts[1].Set();
            });

            Thread th3 = new Thread(() =>
            {
                evts[1].WaitOne(); 
                for (int i = 20; i < 30; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(100);
                } 
            });
            th3.Start();
            th2.Start();
            th.Start();
            Console.WriteLine("線程啓動完畢!"); 

        }

使用信號量其實與第二種方法有些類似,不過這種方式更加靈活,不用等到線程結束。

總結

好了,我要說的就這麼多了,.NetFramework作爲一個比較成熟的技術提供的線程同步方案特別多,若有興趣建議多查查文檔。在文檔裏我們能收穫很多東西。

ps:本文中的代碼只看懂測試,實際這樣寫代碼我是會很生氣的!

發佈了46 篇原創文章 · 獲贊 44 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章