c# 面試必備線程基礎知識點
線程的知識太多,知識點有深有淺,往深的研究會涉及操作系統(tǒng)、CPU、內存,往淺了說就是一些語法。沒有一定的知識積累,很難把線程的知識寫得全面,當然我也沒有這個能力。所以想到一個點寫一個點,盡量總結一些有用的知識點。線程是個大話題,這個系列可能會有好幾遍關于線程的,先從基礎的開始,熱熱身。
一些基礎概念
線程(Thread)是操作系統(tǒng)能夠進行運算調度的最小單位。它是進程中的實際運作單位,一個進程中可以啟動多個線程,每個線程可以并行執(zhí)行不同的任務。嚴格意義上來說,同一時間可以并行運行的線程數(shù)取決于 CPU 的核數(shù)。
根據(jù)線程運行模式,可以把線程分為前臺線程、后臺線程和守護(Daemon)線程:
- 前臺線程:主程序必須等待線程執(zhí)行完畢后才可退出程序。C# 中的 Thread 默認為前臺線程,也可以設置為后臺線程。
- 后臺線程:主程序執(zhí)行完畢立即跟隨退出,不管線程是否執(zhí)行完畢。C# 的 ThreadPool 管理的線程默認為后臺線程。
- 守護線程:守護線程擁有自動結束自己生命周期的特點,它通常被用來執(zhí)行一些后臺任務。
每次開啟一個新的線程都要消耗一定的內存,即使線程什么也不做,也會至少消耗 1M 左右的內存。
多線程并行(Parallelism)和并發(fā)(Concurrency)的區(qū)別:
- 并行:同一時刻有多條指令在多個處理器上同時執(zhí)行,無論從宏觀還是微觀上都是同時發(fā)生的。
- 并發(fā):是指在同一時間段內,宏觀上看多個指令看起來是同時執(zhí)行,微觀上看是多個指令進程在快速的切換執(zhí)行,同一時刻可能只有一條指令被執(zhí)行。
PS:以上概念來源 Google 的多個搜索結果,稍加整理。
Thread、ThreadPool 和 Task
對 C# 開發(fā)者來說,不可不理解清楚 Thread、ThreadPool 和 Task 這三個概念。這也是面試頻率很高的話題,在 StackOverflow 可以找到有很多不錯的回答,我總結整理了一下。
Thread
Thread 是一個實際的操作系統(tǒng)級別的線程(OS 線程),有自己的棧和內核資源。Thread 允許最高程度的控制,你可以 Abort、Suspend 或 Resume 一個線程,你還可以監(jiān)聽它的狀態(tài),設置它的堆棧大小和 Culture 等屬性。Thread 的開銷成本很高,你的每一個線程都會為它的堆棧消耗相對較多的內存,并且在線程之間的處理器上下文切換時會增加額外的 CPU 開銷。
ThreadPool
ThreadPool(線程池)是一堆線程的包裝器,由 CLR 維護。你對線程池中的線程沒有任何控制權,你甚至無法知道線程池什么時候開始執(zhí)行你提交的任務,你只能控制線程池的大小。簡單來說,線程池調用線程的機制是,它首先調用已創(chuàng)建的空閑線程來執(zhí)行你的任務,如果當前沒有空閑線程,可能會創(chuàng)建新線程,也可能會等待。
使用 ThreadPool 可以避免創(chuàng)建太多線程的開銷。但是,如果你向 ThreadPool 提交了太多長時間運行的任務,它可能會被填滿,這時你提交的后面的任務可能最終會等待前面的長時間運行的任務執(zhí)行完成。此外,線程池沒有提供任何方法來檢測一個工作任務何時完成(不像 Thread.Join()),也沒有方法來獲取結果。因此,ThreadPool 最好用于調用者不需要結果的短時操作。
Task
Task 是 TPL(Task Parallel Library)提供一個類,它在 Thread 和 TheadPool 之間提供了兩全其美的解決方案。和 ThreadPool 一樣,Task 并不創(chuàng)建自己的OS 線程。相反,Task 是由 TaskScheduler 調度器執(zhí)行的,默認的調度器只是在 ThreadPool 上運行。
與 ThreadPool 不同的是,Task 還允許你知道它完成的時間,并獲取返回一個結果。你可以在現(xiàn)有的 Task 上調用 ContinueWith(),使它在任務完成后運行更多的代碼(如果它已經完成,就會立即運行回調)。
你也可以通過調用 Wait() 來同步等待一個任務的完成(或者,通過獲取它的 Result 屬性)。與 Thread.Join() 一樣,這將阻塞調用線程,直到任務完成。通常不建議同步等待任務執(zhí)行完成,它使調用線程無法進行任何其他工作。如果當前線程要等待其它線程任務執(zhí)行完成,建議使用 async/await 異步等待,這樣當前線程可以空閑出來去處理其它任務,比如在 await Task.Delay() 時,并不占用線程資源。
由于任務仍然在 ThreadPool 上運行,因此不應該將其用于長時任務的執(zhí)行,因為它們會填滿線程池并阻塞新的工作任務。相反,Task 提供了一個 LongRunning 選項,它將告訴 TaskScheduler 啟用一個新的線程,而不是在 ThreadPool 上運行。
所有較新的上層多線程 API,包括 Parallel.ForEach()、PLINQ、async/await 等,都是建立在 Task 上的。
Thread 和 Task 簡單示例
下面通過一個簡單示例演示 Thread 和 Task 的使用,注意他們是如何創(chuàng)建、傳參、執(zhí)行和等待執(zhí)行完成的。
static void Main(string[] args) { // 創(chuàng)建兩個新的 Thread var thread1 = new Thread(new ThreadStart(() => PerformAction("Thread", 1))); var thread2 = new Thread(new ThreadStart(() => PerformAction("Thread", 2))); // 開始執(zhí)行線程任務 thread1.Start(); thread2.Start(); // 等待兩個線程執(zhí)行完成 thread1.Join(); thread1.Join(); Console.WriteLine("Theads done!"); Console.WriteLine("===我是分隔線==="); // 創(chuàng)建兩個新的 Task var task1 = Task.Run(() => PerformAction("Task", 1)); var task2 = Task.Run(() => PerformAction("Task", 2)); // 執(zhí)行并等待兩個 Task 執(zhí)行完成 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 的對比,對我們編碼的指導意義是:大多數(shù)情況我們應該使用 Task,而不要直接使用 Thread,除非你明確知道你需要一個獨立的線程來執(zhí)行一個長耗時的任務。
小結
本篇內容很基礎,整理了 C# 線程編程有關的重要概念,簡單演示了 Thread 和 Task 的使用。Thread 和 Task 是高頻面試話題,尤其是 Thread 和 Task 的區(qū)別,Thread 更底層,Task 更抽象,回答好這類面試題的關鍵點在 ThreadPool。
作者:精致碼農
以上就是c# 面試必備線程基礎知識點的詳細內容,更多關于c# 線程基礎的資料請關注腳本之家其它相關文章!
相關文章
C#創(chuàng)建windows系統(tǒng)用戶的方法
這篇文章主要介紹了C#創(chuàng)建windows系統(tǒng)用戶的方法,涉及C#操作用戶名、密碼、顯示名稱、描述、是否強制修改密碼、密碼是否過期等技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04Unity性能優(yōu)化Shader函數(shù)ShaderUtil.GetShaderGlobalKeywords用法示例
這篇文章主要為大家介紹了Unity性能優(yōu)化Shader函數(shù)ShaderUtil.GetShaderGlobalKeywords用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09