首頁>其它>

流水線-提升效率

聰明的小明同學,昨天聽了外老師的講解之後,意猶未盡的回家,正琢磨著,怎麼樣使用多執行緒技術,加速大量小任務的處理速度!

就在今天早晨,小明在早餐店看到了這樣一幕:

這是一家非常火爆的早餐店,老闆配備了兩臺收銀機,在早高峰的時候,兩臺收銀機全速執行,以提高顧客點單的速度。

小明點了一份熱乾麵,只見收銀員將小明的訂單遞給了第一位小哥,小哥接到訂單之後,非常熟練地將適量麵條,放入一個錐形的容器中,然後放入身旁的熱鍋中去煮。

這時候,前面的另一個阿姨,正好取走了放在鍋裡的另一份麵條,快速的取一個碗,倒入麵條,加入芝麻醬,還有其他調料,半分鐘左右,這一份熱面就交到了小明前面的一位小姐姐手中。

然後阿姨就開始做小明的那一碗麵條了。。。

小明似乎明白了什麼,他興致勃勃的來到教室,向我講述他買早餐時的所見,說道:

老師,流水線啊!我們可以讓一個執行緒,處理多個小任務,不就可以避免開啟過多執行緒的問題了嗎!這樣在提高任務處理速度的同時,也避免了開啟過多執行緒的昂貴成本。如果我們有多種任務,還可以開啟多個執行緒還處理不同的任務。

這時外老師的臉上,洋溢著滿意的笑容,對小明說到:小明,你前途無量啊!加油,好好學。

觸類旁通

流水線

早餐店的老闆,不可能為每一個顧客,僱一個員工為其提供服務。而是僱幾個員工,分別負責不同的工序。在比較耗時的工序,通常會增加員工的數量,以保證流水線中的每一個員工都不閒著。

流水線同時還有一大好處,就是每一個員工,只需掌握自己這一個工序的技能,每天重複同樣的工作,動作非常的嫻熟。員工培養的成本也非常低。還不用擔心員工自己出去單幹。。。

ThreadPool

這個思路,在軟體行業,也同樣流行。

我們可以自己將任務分類,分組,將每一組任務,交給一個執行緒來處理。我們只需要簡單的程式碼,就可以實現這個邏輯。

當然,軟體行業還有一個特點,就是通常不需要我們自己造輪子,而是直接使用別人造好的輪子。沒錯,微軟已經為我們提供了現成的執行緒池實現ThreadPool。我們直接使用即可。

下面進入例項講解,先回顧一下上一篇文章的示例:

class Data{    public Data(int id)    {        ID = id;    }    public int ID { get; set; }    public void DoSomeThing()    {        ID += 1;    }}static void MultiThread(IList<Data> datas){    foreach (var item in datas)    {        var thd = new System.Threading.Thread(            () => item.DoSomeThing());        thd.Start();        // 這裡先不管 thd.Join();        // 也不要求 DoSomeThing 始終執行    }}

然後我們使用ThreadPool再寫一個測試函式:

static void MultiThreadPool(IList<Data> datas){    foreach (var item in datas)    {        ThreadPool.QueueUserWorkItem(            obj => item.DoSomeThing());        // 我們無法控制 ThreadPool 中的執行緒的 開始、掛起、和中止等        // 這個需要注意的地方    }}

我們在兩個測試函式中,都只將任務新增到多執行緒中去執行,先不考慮其執行結果,單純比較執行緒的新增效率:

public static void Main(){    Console.WriteLine("Hello Thread World!");    var dts = MakeData();    var sw = new Stopwatch();    sw.Start();    MultiThread(dts);    sw.Stop();    var tc1 = sw.ElapsedTicks;    Console.WriteLine($"Multi Thread Time Costs: {tc1}");    sw.Restart();    MultiThreadPool(dts);    sw.Stop();    var tc2 = sw.ElapsedTicks;    Console.WriteLine($"Thread Pool Time Costs: {tc2}");    Console.WriteLine($"Rate: {tc1/tc2}"); // 加入對比    Console.ReadKey();}

編譯執行結果如下:

執行結果

可以看到,ThreadPool的效率,提升了5倍左右。這可能不算特別突出的效率提升。但是當我們把任務的數量提升到1萬個,情況會有什麼不同嗎:

static IList<Data> MakeData(){    var dataNum = 10000; // 任務數量    var datas = new List<Data>(dataNum);    for (int i = 0; i < dataNum; i++)    {        datas.Add(new Data(i));    }    return datas;}

執行結果如下:

1萬個任務結果

可以看到,速度提升已經到了200多倍了。可見任務數量越多,ThreadPool的優勢越明顯。

最後我們將任務數量調整為20個,並在程式碼中,加入檢測執行緒ID的程式碼,然後觀察一下執行緒池中的任務的執行情況:

static IList<Data> MakeData(){    var dataNum = 20; // 任務數量    var datas = new List<Data>(dataNum);    for (int i = 0; i < dataNum; i++)    {        datas.Add(new Data(i));    }    return datas;}class Data{    public Data(int id)    {        ID = id;    }    public int ID { get; set; }    public void DoSomeThing()    {        ID += 1;        Console.WriteLine(            $"Thread ID:{Thread.CurrentThread.ManagedThreadId}" +            $"\t Data ID:{ID}");    }}public static void Main(){    Console.WriteLine("Hello Thread World!");    var dts = MakeData();    MultiThreadPool(dts);    Console.ReadKey();}

執行結果如下:

執行緒ID

可以看出,20個任務,分佈在8個執行緒中執行,其中10號執行緒,執行的任務數量最多。

由此可以看出來,ThreadPool確實可以重複使用執行緒。其中的執行緒,和流水線中的工人一樣,可以重複做一個工序中的工作。

當然,ThreadPool中的執行緒,比工人厲害的地方,就是執行緒可以完成各種各樣的工序,哪個工序需要,就去哪個環節幹活。

所以使用ThreadPool,比現實中的流水線,可以更加靈活,因為每一個工人都是全能選手。

使用ThreadPool注意事項

我們無法控制ThreadPool中的執行緒的開始、掛起、和中止。

長時間執行線上程,不宜放在ThreadPool中,而是應該單獨開啟執行緒,否則會導致執行緒池被長期佔用,而無法執行其他執行緒。

本文中的示例,可能並不完全合適。大家在實際的工作中,要學會舉一反三,觸類旁通,靈活運用。

終於講完了!我看了一眼小明,他又在看著我點頭!

小明同學

踩坑記錄

暫無

下期預告

下面是給同學們準備的乾貨,正在陸續發貨中哦:

多個執行緒之間的資源共享問題

多執行緒死鎖問題

Task

Parallel

await/async

Linq與PLinq (ParallelEnumerable)

。。。 。。。

54
  • 康明斯6bt發動機
  • 《追光吧!哥哥》播出第二期,第一個哥哥進入綜藝嘉賓20強榜單