[C#.NET 拾遺補漏]11:面試必備線程基礎知識點

線程的知識太多,知識點有深有淺,往深的研究會涉及操做系統、CPU、內存,往淺了說就是一些語法。沒有必定的知識積累,很難把線程的知識寫得全面,固然我也沒有這個能力。因此想到一個點寫一個點,儘可能總結一些有用的知識點。線程是個大話題,這個系列可能會有好幾遍關於線程的,先從基礎的開始,熱熱身。面試

一些基礎概念

線程(Thread)是操做系統可以進行運算調度的最小單位。它是進程中的實際運做單位,一個進程中能夠啓動多個線程,每一個線程能夠並行執行不一樣的任務。嚴格意義上來講,同一時間能夠並行運行的線程數取決於 CPU 的核數。編程

根據線程運行模式,能夠把線程分爲前臺線程、後臺線程和守護(Daemon)線程:多線程

  • 前臺線程:主程序必須等待線程執行完畢後纔可退出程序。C# 中的 Thread 默認爲前臺線程,也能夠設置爲後臺線程。併發

  • 後臺線程:主程序執行完畢當即跟隨退出,無論線程是否執行完畢。C# 的 ThreadPool 管理的線程默認爲後臺線程。dom

  • 守護線程:守護線程擁有自動結束本身生命週期的特色,它一般被用來執行一些後臺任務。異步

每次開啓一個新的線程都要消耗必定的內存,即便線程什麼也不作,也會至少消耗 1M 左右的內存。async

多線程並行(Parallelism)和併發(Concurrency)的區別:編碼

  • 並行:同一時刻有多條指令在多個處理器上同時執行,不管從宏觀仍是微觀上都是同時發生的。
  • 併發:是指在同一時間段內,宏觀上看多個指令看起來是同時執行,微觀上看是多個指令進程在快速的切換執行,同一時刻可能只有一條指令被執行。

PS:以上概念來源 Google 的多個搜索結果,稍加整理。操作系統

Thread、ThreadPool 和 Task

對 C# 開發者來講,不可不理解清楚 Thread、ThreadPool 和 Task 這三個概念。這也是面試頻率很高的話題,在 StackOverflow 能夠找到有不少不錯的回答,我總結整理了一下。線程

Thread

Thread 是一個實際的操做系統級別的線程(OS 線程),有本身的棧和內核資源。Thread 容許最高程度的控制,你能夠 Abort、Suspend 或 Resume 一個線程,你還能夠監聽它的狀態,設置它的堆棧大小和 Culture 等屬性。Thread 的開銷成本很高,你的每個線程都會爲它的堆棧消耗相對較多的內存,而且在線程之間的處理器上下文切換時會增長額外的 CPU 開銷。

ThreadPool

ThreadPool(線程池)是一堆線程的包裝器,由 CLR 維護。你對線程池中的線程沒有任何控制權,你甚至沒法知道線程池何時開始執行你提交的任務,你只能控制線程池的大小。簡單來講,線程池調用線程的機制是,它首先調用已建立的空閒線程來執行你的任務,若是當前沒有空閒線程,可能會建立新線程,也可能會等待。

使用 ThreadPool 能夠避免建立太多線程的開銷。可是,若是你向 ThreadPool 提交了太多長時間運行的任務,它可能會被填滿,這時你提交的後面的任務可能最終會等待前面的長時間運行的任務執行完成。此外,線程池沒有提供任何方法來檢測一個工做任務什麼時候完成(不像 Thread.Join()),也沒有方法來獲取結果。所以,ThreadPool 最好用於調用者不須要結果的短時操做。

Task

Task 是 TPL(Task Parallel Library)提供一個類,它在 Thread 和 TheadPool 之間提供了一箭雙鵰的解決方案。和 ThreadPool 同樣,Task 並不建立本身的OS 線程。相反,Task 是由 TaskScheduler 調度器執行的,默認的調度器只是在 ThreadPool 上運行。

與 ThreadPool 不一樣的是,Task 還容許你知道它完成的時間,並獲取返回一個結果。你能夠在現有的 Task 上調用 ContinueWith(),使它在任務完成後運行更多的代碼(若是它已經完成,就會當即運行回調)。

你也能夠經過調用 Wait() 來同步等待一個任務的完成(或者,經過獲取它的 Result 屬性)。與 Thread.Join() 同樣,這將阻塞調用線程,直到任務完成。一般不建議同步等待任務執行完成,它使調用線程沒法進行任何其餘工做。若是當前線程要等待其它線程任務執行完成,建議使用 async/await 異步等待,這樣當前線程能夠空閒出來去處理其它任務,好比在 await Task.Delay() 時,並不佔用線程資源。

因爲任務仍然在 ThreadPool 上運行,所以不該該將其用於長時任務的執行,由於它們會填滿線程池並阻塞新的工做任務。相反,Task 提供了一個 LongRunning 選項,它將告訴 TaskScheduler 啓用一個新的線程,而不是在 ThreadPool 上運行。

全部較新的上層多線程 API,包括 Parallel.ForEach()、PLINQ、async/await 等,都是創建在 Task 上的。

Thread 和 Task 簡單示例

下面經過一個簡單示例演示 Thread 和 Task 的使用,注意他們是如何建立、傳參、執行和等待執行完成的。

static void Main(string[] args)
{
    // 建立兩個新的 Thread
    var thread1 = new Thread(new ThreadStart(() => PerformAction("Thread", 1)));
    var thread2 = new Thread(new ThreadStart(() => PerformAction("Thread", 2)));

    // 開始執行線程任務
    thread1.Start();
    thread2.Start();

    // 等待兩個線程執行完成
    thread1.Join();
    thread2.Join();

    Console.WriteLine("Theads done!");

    Console.WriteLine("===我是分隔線===");

    // 建立兩個新的 Task
    var task1 = Task.Run(() => PerformAction("Task", 1));
    var task2 = Task.Run(() => PerformAction("Task", 2));

    // 執行並等待兩個 Task 執行完成
    Task.WaitAll(new[] { task1, task2 });

    Console.WriteLine("Tasks done!");

    Console.ReadKey();
}

static void PerformAction(string threadOrTask, int id)
{
    var rnd = new Random(id);
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"{threadOrTask}: {id}: {i}", id, i);
        Thread.Sleep(rnd.Next(0, 1000));
    }
}

運行效果:

注意到,相比之下 Task 比 Thread 好用得多,加上前文 Task 和 Thread 的對比,對咱們編碼的指導意義是:大多數狀況咱們應該使用 Task,而不要直接使用 Thread,除非你明確知道你須要一個獨立的線程來執行一個長耗時的任務。

小結

本篇內容很基礎,整理了 C# 線程編程有關的重要概念,簡單演示了 Thread 和 Task 的使用。Thread 和 Task 是高頻面試話題,尤爲是 Thread 和 Task 的區別,Thread 更底層,Task 更抽象,回答好這類面試題的關鍵點在 ThreadPool。

下一篇將繼續講解關於線程的話題,敬請期待!

相關文章
相關標籤/搜索