網(wǎng)站首頁 編程語言 正文
C#多線程學(xué)習(xí)之Thread、ThreadPool、Task、Parallel四者區(qū)別_C#教程
作者:Alan.hsiang ? 更新時(shí)間: 2022-02-21 編程語言線程(英語:thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。進(jìn)程是資源分配的基本單位。所有與該進(jìn)程有關(guān)的資源,都被記錄在進(jìn)程控制塊PCB中。以表示該進(jìn)程擁有這些資源或正在使用它們。本文以一些簡單的小例子,簡述多線程的發(fā)展歷程【Thread,ThreadPool,Task,Parallel】,僅供學(xué)習(xí)分享使用,如有不足之處,還請指正。
Thread
Thread做為早期【.Net Framework1.0】的.Net提供的多線程方案,提供了很多的封裝方法,來操作線程。具體如下所示:
- Start方法,用于啟動(dòng)一個(gè)線程。線程的狀態(tài)變更為Running。
- Suspend方法,掛起一個(gè)線程,或者如果線程狀態(tài)為已掛起,則不起作用。
- Resume方法,如果線程為已掛起,這繼續(xù)運(yùn)行。
- Join方法,等待線程,直到線程結(jié)束,也可以設(shè)置等待時(shí)間。
- Abort方法,強(qiáng)制終止線程,跑出ThreadAbortException異常。
- 其他線程屬性:IsBackground是否后臺線程,IsThreadPoolThread是否線程池線程,IsAlive線程是否運(yùn)行,Priority線程優(yōu)先級,ThreadState當(dāng)前線程狀態(tài),ManagedThreadId線程唯一標(biāo)識等
通過Thread可以單獨(dú)的開啟一個(gè)線程,通過構(gòu)造函數(shù)來創(chuàng)建線程對象,可以是無參數(shù)也可以是帶參數(shù)。其中參數(shù)ThreadStart是一個(gè)無參數(shù)委托,ParameterizedThreadStart為一個(gè)帶參數(shù)委托。
無參數(shù),示例如下所示:
private void btnThread_Click(object sender, EventArgs e) { ThreadStart threadStart = new ThreadStart(DoSomethingLong); Thread thread = new Thread(threadStart); thread.Start(); } private void DoSomethingLong() { string name = "Thread"; Console.WriteLine("************DoSomethingLong 開始 name= {0} 線程ID= {1} 時(shí)間 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU計(jì)算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 結(jié)束 name= {0} 線程ID= {1} 時(shí)間 = {2} 結(jié)果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
示例結(jié)果如下所示:
帶參數(shù)示例,如下所示:
private void btnThread2_Click(object sender, EventArgs e) { ParameterizedThreadStart threadStart = new ParameterizedThreadStart(DoSomethingLongWithParam); Thread thread = new Thread(threadStart); string name = "Param"; thread.Start(name); } private void DoSomethingLongWithParam(object name) { Console.WriteLine("************DoSomethingLongWithParam 開始 name= {0} 線程ID= {1} 時(shí)間 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU計(jì)算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLongWithParam 結(jié)束 name= {0} 線程ID= {1} 時(shí)間 = {2} 結(jié)果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
帶參數(shù)示例結(jié)果,如下所示:
通過對以上示例進(jìn)行分析,得出結(jié)論如下所示:
- 線程是由操作系統(tǒng)進(jìn)行創(chuàng)建的,Thread提供的方法只是對底層方法的封裝。比如執(zhí)行對應(yīng)方法后,操作系統(tǒng)并不會(huì)立即執(zhí)行相應(yīng)的操作,而要CPU時(shí)間片輪轉(zhuǎn)后才會(huì)執(zhí)行響應(yīng)。
- Thread創(chuàng)建線程過于松散,缺乏管理,例如,如果同時(shí)創(chuàng)建10000個(gè)線程,程序也不會(huì)報(bào)錯(cuò),但是系統(tǒng)可能無法承載如此多的線程而導(dǎo)致崩潰。
- Thread的頻繁創(chuàng)建和銷毀,也會(huì)消耗系統(tǒng)資源。
ThreadPool
為了應(yīng)對Thread創(chuàng)建缺乏管理的問題,在后續(xù)版本【.Net Framework2.0】中推出了線程池的概念。那什么是線程池呢?
池化資源管理設(shè)計(jì)思想:線程是一種資源,之前每次需要線程,都是去創(chuàng)建線程,使用完成后,再釋放掉。池化,就是做一個(gè)容器,容器提前申請指定數(shù)量的線程,需要用到線程的時(shí)候,直接到線程池中取,用完之后再放回容器【通過控制狀態(tài)標(biāo)識線程是否正在被使用】。避免頻繁的創(chuàng)建和銷毀,容器還會(huì)根據(jù)限制的數(shù)量取申請和釋放。
關(guān)于通過線程池創(chuàng)建多線程,具體示例如下:
private void btnThread3_Click(object sender, EventArgs e) { WaitCallback waitCallback = new WaitCallback(DoSomethingLongWithParam); string name = "ThreadPool"; ThreadPool.QueueUserWorkItem(waitCallback,name); }
線程池示例執(zhí)行結(jié)果如下所示:
通過對線程池執(zhí)行結(jié)果進(jìn)行分析,得出結(jié)論如下:
- 兩次執(zhí)行結(jié)果,均為同一個(gè)線程ID,說明線程用完并未銷毀,而是放回線程池子,待下次使用時(shí)重新取出,繼續(xù)使用。
- 通過線程池可以有效的控制線程并發(fā)的數(shù)量,避免資源的浪費(fèi)。
- 通過分析源碼發(fā)現(xiàn),ThreadPool線程池提供的接口較少,在線程等待和交互方面不太友好。
Task
隨著.Net版本的演化,后續(xù)版本【.Net Framework3.0】推出了Task做為多線程解決方案。默認(rèn)情況下,可以通過構(gòu)造函數(shù)創(chuàng)建Task,示例如下:
private void btnTask_Click(object sender, EventArgs e) { Action action = new Action(DoSomethingLong); Task task = new Task(action); task.Start(); }
默認(rèn)Task示例,執(zhí)行結(jié)果如下:
通過對以上Task示例和源碼進(jìn)行分析,得出結(jié)論如下:
- Task產(chǎn)生的線程,全部都是線程池線程。
- Task提供的豐富的API,便于開發(fā)實(shí)踐。
Parallel
Parallel提供對并行線程的支持,可以通過Parallel同時(shí)發(fā)起多個(gè)線程,在某些方面具有應(yīng)用優(yōu)勢,默認(rèn)示例如下所示:
private void btnParallel_Click(object sender, EventArgs e) { Action action = new Action(DoSomethingLong); Parallel.Invoke(action,action,action); }
Parallel的Invoke方法執(zhí)行,結(jié)果如下:
通過對Parallel的Invoke示例方法進(jìn)行分析,得出結(jié)論如下:
- Parallel的Invoke方法,可以同時(shí)開啟多個(gè)線程,同時(shí)主線程【線程ID=1】也會(huì)參與計(jì)算,即頁面也會(huì)卡住。
- Parallel可以通過ParallelOptions.MaxDegreeOfParallelism指定并發(fā)數(shù)量。
Task專講
以下面的一個(gè)場景為例進(jìn)行說明:
假如開發(fā)一個(gè)系統(tǒng),流程如下:
1. 前期的需求調(diào)研,需求分析,系統(tǒng)設(shè)計(jì),詳細(xì)設(shè)計(jì)(順序執(zhí)行,是開發(fā)編碼的前提)
2.按模塊開發(fā)【中間階段,可多人同時(shí)工作】
3.測試【順序執(zhí)行,是開發(fā)編碼的后續(xù)工作】
分析:以上三個(gè)階段,每一個(gè)階段又可以細(xì)分?jǐn)?shù)個(gè)小階段,其中有些階段是順序執(zhí)行的,有些階段又可以并行執(zhí)行。
以代碼的形式進(jìn)行描述,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //開發(fā)前的工作 Console.WriteLine("組建團(tuán)隊(duì)"); Console.WriteLine("需求分析"); Console.WriteLine("系統(tǒng)設(shè)計(jì)"); Console.WriteLine("詳細(xì)設(shè)計(jì)"); //開始開發(fā) Task.Run(() => { Coding("張三", "接口"); }); Task.Run(() => { Coding("李四", "前端頁面"); }); Task.Run(() => { Coding("王五", "手機(jī)App"); }); Task.Run(() => { Coding("劉大", "后端業(yè)務(wù)"); }); //開發(fā)后的工作 Console.WriteLine("alpha測試"); Console.WriteLine("beta測試"); Console.WriteLine("uat測試"); Console.WriteLine("系統(tǒng)上線"); } private void Coding(string developer,string model) { Console.WriteLine("【Begin】在{0},{1}開始開發(fā){2},線程id為{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer,model, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); Console.WriteLine("【 End 】在{0},{1}完成開發(fā){2},線程id為{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer, model, Thread.CurrentThread.ManagedThreadId); }
示例運(yùn)行結(jié)果,如下所示:
通過分析以上示例,發(fā)現(xiàn)程序并未按照預(yù)期的運(yùn)行,很明顯的一點(diǎn):測試跑到了開發(fā)前面。
為了解決上述順序錯(cuò)亂的問題,Task提供了WaitAll方法,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //開發(fā)前的工作 Console.WriteLine("組建團(tuán)隊(duì)"); Console.WriteLine("需求分析"); Console.WriteLine("系統(tǒng)設(shè)計(jì)"); Console.WriteLine("詳細(xì)設(shè)計(jì)"); //開始開發(fā) List<Task> tasks = new List<Task>(); tasks.Add(Task.Run(() => { Coding("張三", "接口"); })); tasks.Add(Task.Run(() => { Coding("李四", "前端頁面"); })); tasks.Add(Task.Run(() => { Coding("王五", "手機(jī)App"); })); tasks.Add(Task.Run(() => { Coding("劉大", "后端業(yè)務(wù)"); })); Task.WaitAll(tasks.ToArray()); //開發(fā)后的工作 Console.WriteLine("alpha測試"); Console.WriteLine("beta測試"); Console.WriteLine("uat測試"); Console.WriteLine("系統(tǒng)上線"); }
運(yùn)行示例,結(jié)果如下所示:
通過運(yùn)行以上示例,發(fā)現(xiàn):順序確實(shí)符合預(yù)期,可以滿足要求,但是程序會(huì)卡住,這點(diǎn)不太友好。
如何才能優(yōu)雅的控制先后順序呢?Task還提供了TaskFactory,如下所示:
private void btnTask2_Click(object sender, EventArgs e) { //開發(fā)前的工作 Console.WriteLine("組建團(tuán)隊(duì)"); Console.WriteLine("需求分析"); Console.WriteLine("系統(tǒng)設(shè)計(jì)"); Console.WriteLine("詳細(xì)設(shè)計(jì)"); //開始開發(fā) List<Task> tasks = new List<Task>(); tasks.Add(Task.Run(() => { Coding("張三", "接口"); })); tasks.Add(Task.Run(() => { Coding("李四", "前端頁面"); })); tasks.Add(Task.Run(() => { Coding("王五", "手機(jī)App"); })); tasks.Add(Task.Run(() => { Coding("劉大", "后端業(yè)務(wù)"); })); TaskFactory taskFactory = new TaskFactory(); taskFactory.ContinueWhenAll(tasks.ToArray(), new Action<Task[]>((taskArray) => { //開發(fā)后的工作 Console.WriteLine("alpha測試"); Console.WriteLine("beta測試"); Console.WriteLine("uat測試"); Console.WriteLine("系統(tǒng)上線"); })); }
TaskFactory示例測試,如下所示:
通過對示例進(jìn)行分析,得出如下結(jié)論:
- 業(yè)務(wù)邏輯上已要求,頁面也不會(huì)卡頓,優(yōu)雅的實(shí)現(xiàn)了多線程的操作。
- Task產(chǎn)生的線程,為線程池線程。
- TaskFactory不僅提供了ContinueWhenAll等待所有線程,還提供了ContinueWhenAny等待任意線程。
原文鏈接:https://www.cnblogs.com/hsiang/p/15690604.html
相關(guān)推薦
- 2022-10-11 XGBoost與GBDT和LGBM區(qū)別
- 2022-10-04 C語言零基礎(chǔ)徹底掌握預(yù)處理上篇_C 語言
- 2022-07-22 React警告:Can‘t perform a React state update on an u
- 2022-09-16 Android?運(yùn)用@JvmName解決函數(shù)簽名沖突問題詳解_Android
- 2022-04-25 ASP.NET?Core中Razor頁面與MVC區(qū)別介紹_實(shí)用技巧
- 2021-12-02 C++11標(biāo)準(zhǔn)庫bind函數(shù)應(yīng)用教程_C 語言
- 2022-06-11 python中Event實(shí)現(xiàn)線程間同步介紹_python
- 2023-01-13 Go?cobra庫使用教程_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支