題目
比如我們有三個方法,類似以下代碼
<code> static void Methond1() { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第一個方法=="); } static void Methond2() { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第二個方法======="); } static void Methond3() { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第三個方法======="); } }/<code>
分別初始化三個線程去執行這三個方法,每個線程執行的方法可以任意指定,但是方法的執行順序必須是:Methond1=》Methond2=》Methond3
示例:
<code>輸入:3,2,1 第一個線程執行方法3,第二個線程執行方法2,第三個線程執行方法1 輸出:Methond1的輸出=》Methond2的輸出=》Methond3 的輸出 ,即: xxx執行 第一個方法== xxx執行 第二個方法== xxx執行 第三個方法==/<code>
題解分析
這道題本質上考的是多線程環境資源競爭的問題,在多線程環境下要想以固定某個順序執行方法,必須要有一個類似屏障的東西來阻止不該執行的線程,或者換句話說必須要有一個機制來順序化多個線程。這也是我們平時多線程開發環境中必須要面對的問題,現代服務器的cpu都是多核心,我們平時用的最多的java,c#等高級語言是能夠利用服務器多核心這個優勢的,這裡是一個進程內,多線程就可以利用多核心。當然也有一些別的技術,比如Nodejs,雖然執行是單線程的,但是可以利用多進程來提高多物理cpu的利用率。
說的有點遠了,接下來還是看這道題,這道題目考驗的是多線程下資源的競爭問題和線程間的通信問題,說的直白一點就是:Methond1未執行之前,任何方法都不能執行(這裡是指方法內的代碼),Methond1執行完之後,會通知執行Methond2的線程,Methond2開始執行,Methond2執行完畢會通知執行Methond3的線程,以此類推。所以這道題的關鍵在於阻塞某個線程執行和通知某個線程執行。
在很多語言中都提供了lock的機制,或者信號量的機制。這些都是可以實現線程間通信的的解決方案,當然最簡單暴力的方式還是通過共享變量的方式來實現通知,雖然有很多缺陷,但是在很多情況下確實是最簡單快捷的方案。下邊就以c#語言為例:
<code> class Program { //把三個方法分別對應到 委託,為了方便執行方法 static Action FirstAction = Methond1; static Action SecondAction = Methond2; static Action ThirdAction = Methond3; static Dictionary ActionDic = new Dictionary() { { 1, FirstAction }, { 2, SecondAction }, { 3, ThirdAction } }; static void Main(string[] args) { while (true) { Console.WriteLine("請輸入順序"); var inputStr = Console.ReadLine(); IsFinish1 = IsFinish2 = false; var input = inputStr.Split(",").Select(s => int.Parse(s)); foreach (var i in input) { var method = ActionDic.GetValueOrDefault(i); Thread t = new Thread(method.Invoke); t.Name = "線程" + i; Console.WriteLine(#34;{t.Name} 執行方法 {method.Method.Name}"); t.Start(); } } } static object objLock = new object(); static bool IsFinish1 = false; //第一方法是否執行完畢 static bool IsFinish2 = false; //第二方法是否執行完畢 static void Methond1() { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第一個方法=="); IsFinish1 = true; } static void Methond2() { while (true) { if (IsFinish1) { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第二個方法======="); IsFinish2 = true; break; } } } static void Methond3() { while (true) { if (IsFinish2) { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第三個方法======="); break; } } } }/<code>
測試執行:
<code>請輸入順序 2,3,1 線程2 執行方法 Methond2 線程3 執行方法 Methond3 線程1 執行方法 Methond1 請輸入順序 線程1 執行 第一個方法== 線程2 執行 第二個方法======= 線程3 執行 第三個方法======= 3,2,1 線程3 執行方法 Methond3 線程2 執行方法 Methond2 線程1 執行方法 Methond1 請輸入順序 線程1 執行 第一個方法== 線程2 執行 第二個方法======= 線程3 執行 第三個方法=======/<code>
不要太糾結以上的代碼,以上只是作為拋磚引玉的作用,就算是有執行錯誤,思路我覺得還是沒有問題的,當然確實有很多要優化的地方,比如等待執行的線程一直在耗費cpu資源的情況,可以替換為自旋鎖或者混合鎖,有興趣的同學可以進行優化一番。
另闢蹊徑
但是,作為語法很優秀的c#怎麼能就此止步呢?c#本身提供了Task的機制,可以把它看做是類似golang的協程(雖然不是很像)。基於task的機制我們很容易就完成,task的 ContinueWith機制就是為此而生的,它可以很方便的把多個並行的task串行化,有興趣的同學可以試一下,但是我要說的並非ContinueWith。既然要執行的順序化,如果我把執行的代碼放在有順序的容器中,然後按照順序去執行,豈不快哉?不多廢話:
```
<code>class TaskTest { static Func FirstAction = Methond1; static Func SecondAction = Methond2; static Func ThirdAction = Methond3; static Dictionary> ActionDic = new Dictionary>() { { 1, FirstAction }, { 2, SecondAction }, { 3, ThirdAction } }; static Func[] tList = new Func[3]; public static void Start() { while (true) { Console.WriteLine("請輸入順序"); var inputStr = Console.ReadLine(); var input = inputStr.Split(",").Select(s => int.Parse(s)); foreach (var i in input) { var method = ActionDic.GetValueOrDefault(i); tList[i-1] = method; } foreach (var t in tList) { t.Invoke().Wait(); } } } static Task Methond1() { return Task.Run(()=> { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第一個方法=="); }); } static Task Methond2() { return Task.Run(() => { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第二個方法======="); }); } static Task Methond3() { return Task.Run(() => { Console.WriteLine(#34;{Thread.CurrentThread.Name} 執行 第三個方法======="); }); } }/<code>
測試:
<code> 請輸入順序 2,3,1 執行 第一個方法== 執行 第二個方法======= 執行 第三個方法======= 請輸入順序 3,2,1 執行 第一個方法== 執行 第二個方法======= 執行 第三個方法======= 請輸入順序 1,3,2 執行 第一個方法== 執行 第二個方法======= 執行 第三個方法======= 請輸入順序/<code>
是不是很爽,毫不猶豫的說c#是一門很優秀的語言,在併發方面做的很優秀。以上Task的代碼並非是最優代碼,比如 可以用Await來代替 wait(),這樣的話在真實環境中可以釋放當前線程來提高吞吐量,記住:await方式提高的吞吐量,並非縮短了單次請求的執行時間。