C#多線程系列之線程的創(chuàng)建和生命周期
1,獲取當(dāng)前線程信息
Thread.CurrentThread
是一個(gè) 靜態(tài)的 Thread 類,Thread 的CurrentThread
屬性,可以獲取到當(dāng)前運(yùn)行線程的一些信息,其定義如下:
public static System.Threading.Thread CurrentThread { get; }
Thread 類有很多屬性和方法,這里就不列舉了,后面的學(xué)習(xí)會(huì)慢慢熟悉更多 API 和深入了解使用。
這里有一個(gè)簡(jiǎn)單的示例:
static void Main(string[] args) { Thread thread = new Thread(OneTest); thread.Name = "Test"; thread.Start(); Console.ReadKey(); } public static void OneTest() { Thread thisTHread = Thread.CurrentThread; Console.WriteLine("線程標(biāo)識(shí):" + thisTHread.Name); Console.WriteLine("當(dāng)前地域:" + thisTHread.CurrentCulture.Name); // 當(dāng)前地域 Console.WriteLine("線程執(zhí)行狀態(tài):" + thisTHread.IsAlive); Console.WriteLine("是否為后臺(tái)線程:" + thisTHread.IsBackground); Console.WriteLine("是否為線程池線程"+thisTHread.IsThreadPoolThread); }
輸出
線程標(biāo)識(shí):Test 當(dāng)前地域:zh-CN 線程執(zhí)行狀態(tài):True 是否為后臺(tái)線程:False 是否為線程池線程False
2,管理線程狀態(tài)
一般認(rèn)為,線程有五種狀態(tài):
新建(new 對(duì)象) 、就緒(等待CPU調(diào)度)、運(yùn)行(CPU正在運(yùn)行)、阻塞(等待阻塞、同步阻塞等)、死亡(對(duì)象釋放)。
理論的東西不說(shuō)太多,直接擼代碼。
2.1 啟動(dòng)與參數(shù)傳遞
新建線程簡(jiǎn)直滾瓜爛熟,無(wú)非 new
一下,然后 Start()
。
Thread thread = new Thread();
Thread 的構(gòu)造函數(shù)有四個(gè):
public Thread(ParameterizedThreadStart start); public Thread(ThreadStart start); public Thread(ParameterizedThreadStart start, int maxStackSize); public Thread(ThreadStart start, int maxStackSize);
我們以啟動(dòng)新的線程時(shí)傳遞參數(shù)來(lái)舉例,使用這四個(gè)構(gòu)造函數(shù)呢?
2.1.1 ParameterizedThreadStart
ParameterizedThreadStart 是一個(gè)委托,構(gòu)造函數(shù)傳遞的參數(shù)為需要執(zhí)行的方法,然后在 Start
方法中傳遞參數(shù)。
需要注意的是,傳遞的參數(shù)類型為 object,而且只能傳遞一個(gè)。
代碼示例如下:
static void Main(string[] args) { string myParam = "abcdef"; ParameterizedThreadStart parameterized = new ParameterizedThreadStart(OneTest); Thread thread = new Thread(parameterized); thread.Start(myParam); Console.ReadKey(); } public static void OneTest(object obj) { string str = obj as string; if (string.IsNullOrEmpty(str)) return; Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); Console.WriteLine(str); }
2.1.2 使用靜態(tài)變量或類成員變量
此種方法不需要作為參數(shù)傳遞,各個(gè)線程共享堆棧。
優(yōu)點(diǎn)是不需要裝箱拆箱,多線程可以共享空間;缺點(diǎn)是變量是大家都可以訪問(wèn),此種方式在多線程競(jìng)價(jià)時(shí),可能會(huì)導(dǎo)致多種問(wèn)題(可以加鎖解決)。
下面使用兩個(gè)變量實(shí)現(xiàn)數(shù)據(jù)傳遞:
class Program { private string A = "成員變量"; public static string B = "靜態(tài)變量"; static void Main(string[] args) { // 創(chuàng)建一個(gè)類 Program p = new Program(); Thread thread1 = new Thread(p.OneTest1); thread1.Name = "Test1"; thread1.Start(); Thread thread2 = new Thread(OneTest2); thread2.Name = "Test2"; thread2.Start(); Console.ReadKey(); } public void OneTest1() { Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); Console.WriteLine(A); // 本身對(duì)象的其它成員 } public static void OneTest2() { Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); Console.WriteLine(B); // 全局靜態(tài)變量 } }
2.1.3 委托與Lambda
原理是 Thread 的構(gòu)造函數(shù) public Thread(ThreadStart start);
,ThreadStart
是一個(gè)委托,其定義如下
public delegate void ThreadStart();
使用委托的話,可以這樣寫(xiě)
static void Main(string[] args) { System.Threading.ThreadStart start = DelegateThread; Thread thread = new Thread(start); thread.Name = "Test"; thread.Start(); Console.ReadKey(); } public static void DelegateThread() { OneTest("a", "b", 666, new Program()); } public static void OneTest(string a, string b, int c, Program p) { Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); }
有那么一點(diǎn)點(diǎn)麻煩,不過(guò)我們可以使用 Lambda 快速實(shí)現(xiàn)。
使用 Lambda 示例如下:
static void Main(string[] args) { Thread thread = new Thread(() => { OneTest("a", "b", 666, new Program()); }); thread.Name = "Test"; thread.Start(); Console.ReadKey(); } public static void OneTest(string a, string b, int c, Program p) { Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); }
提示:如果需要處理的算法比較簡(jiǎn)單的話,可以直接寫(xiě)進(jìn)委托中,不需要另外寫(xiě)方法啦。
可以看到,C# 是多么的方便。
2.2 暫停與阻塞
Thread.Sleep()
方法可以將當(dāng)前線程掛起一段時(shí)間,Thread.Join()
方法可以阻塞當(dāng)前線程一直等待另一個(gè)線程運(yùn)行至結(jié)束。
在等待線程 Sleep()
或 Join()
的過(guò)程中,線程是阻塞的(Blocket)。
阻塞的定義:當(dāng)線程由于特點(diǎn)原因暫停執(zhí)行,那么它就是阻塞的。
如果線程處于阻塞狀態(tài),線程就會(huì)交出他的 CPU 時(shí)間片,并且不會(huì)消耗 CPU 時(shí)間,直至阻塞結(jié)束。
阻塞會(huì)發(fā)生上下文切換。
代碼示例如下:
static void Main(string[] args) { Thread thread = new Thread(OneTest); thread.Name = "小弟弟"; Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街"); Console.WriteLine("吃完飯了"); Console.WriteLine($"{DateTime.Now}:小弟弟開(kāi)始玩游戲"); thread.Start(); // 化妝 5 s Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5)); Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲"); thread.Join(); Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false")); Console.WriteLine($"{DateTime.Now}:走,逛街去"); Console.ReadKey(); } public static void OneTest() { Console.WriteLine(Thread.CurrentThread.Name + "開(kāi)始打游戲"); for (int i = 0; i < 10; i++) { Console.WriteLine($"{DateTime.Now}:第幾局:" + i); Thread.Sleep(TimeSpan.FromSeconds(2)); // 休眠 2 秒 } Console.WriteLine(Thread.CurrentThread.Name + "打完了"); }
Join() 也可以實(shí)現(xiàn)簡(jiǎn)單的線程同步,即一個(gè)線程等待另一個(gè)線程完成。
2.3 線程狀態(tài)
ThreadState
是一個(gè)枚舉,記錄了線程的狀態(tài),我們可以從中判斷線程的生命周期和健康情況。
其枚舉如下:
枚舉 | 值 | 說(shuō)明 |
---|---|---|
Initialized | 0 | 此狀態(tài)指示線程已初始化但尚未啟動(dòng)。 |
Ready | 1 | 此狀態(tài)指示線程因無(wú)可用的處理器而等待使用處理器。 線程準(zhǔn)備在下一個(gè)可用的處理器上運(yùn)行。 |
Running | 2 | 此狀態(tài)指示線程當(dāng)前正在使用處理器。 |
Standby | 3 | 此狀態(tài)指示線程將要使用處理器。 一次只能有一個(gè)線程處于此狀態(tài)。 |
Terminated | 4 | 此狀態(tài)指示線程已完成執(zhí)行并已退出。 |
Transition | 6 | 此狀態(tài)指示線程在可以執(zhí)行前等待處理器之外的資源。 例如,它可能正在等待其執(zhí)行堆棧從磁盤(pán)中分頁(yè)。 |
Unknown | 7 | 線程的狀態(tài)未知。 |
Wait | 5 | 此狀態(tài)指示線程尚未準(zhǔn)備好使用處理器,因?yàn)樗诘却鈬僮魍瓿苫虻却Y源釋放。 當(dāng)線程就緒后,將對(duì)其進(jìn)行重排。 |
但是里面有很多枚舉類型是沒(méi)有用處的,我們可以使用一個(gè)這樣的方法來(lái)獲取更加有用的信息:
public static ThreadState GetThreadState(ThreadState ts) { return ts & (ThreadState.Unstarted | ThreadState.WaitSleepJoin | ThreadState.Stopped); }
根據(jù) 2.2 中的示例,我們修改一下 Main 中的方法:
static void Main(string[] args) { Thread thread = new Thread(OneTest); thread.Name = "小弟弟"; Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街"); Console.WriteLine("吃完飯了"); Console.WriteLine($"{DateTime.Now}:小弟弟開(kāi)始玩游戲"); Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState))); thread.Start(); Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState))); // 化妝 5 s Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5)); Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState))); Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲"); thread.Join(); Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState))); Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false")); Console.WriteLine($"{DateTime.Now}:走,逛街去"); Console.ReadKey(); }
代碼看著比較亂,請(qǐng)復(fù)制到項(xiàng)目中運(yùn)行一下。
輸出示例:
2020/4/11 11:01:48:大家在吃飯,吃完飯后要帶小弟弟逛街 吃完飯了 2020/4/11 11:01:48:小弟弟開(kāi)始玩游戲 弟弟在干嘛?(線程狀態(tài)):Unstarted 弟弟在干嘛?(線程狀態(tài)):Running 不管他,大姐姐化妝先 小弟弟開(kāi)始打游戲 2020/4/11 11:01:48:第幾局:0 2020/4/11 11:01:50:第幾局:1 2020/4/11 11:01:52:第幾局:2 弟弟在干嘛?(線程狀態(tài)):WaitSleepJoin 2020/4/11 11:01:53:化完妝,等小弟弟打完游戲 2020/4/11 11:01:54:第幾局:3 2020/4/11 11:01:56:第幾局:4 2020/4/11 11:01:58:第幾局:5 2020/4/11 11:02:00:第幾局:6 2020/4/11 11:02:02:第幾局:7 2020/4/11 11:02:04:第幾局:8 2020/4/11 11:02:06:第幾局:9 小弟弟打完了 弟弟在干嘛?(線程狀態(tài)):Stopped 打完游戲了嘛?true 2020/4/11 11:02:08:走,逛街去
可以看到 Unstarted
、WaitSleepJoin
、Running
、Stopped
四種狀態(tài),即未開(kāi)始(就緒)、阻塞、運(yùn)行中、死亡。
2.4 終止
.Abort()
方法不能在 .NET Core 上使用,不然會(huì)出現(xiàn) System.PlatformNotSupportedException:“Thread abort is not supported on this platform.”
。
后面關(guān)于異步的文章會(huì)講解如何實(shí)現(xiàn)終止。
由于 .NET Core 不支持,就不理會(huì)這兩個(gè)方法了。這里只列出 API,不做示例。
方法 | 說(shuō)明 |
---|---|
Abort() | 在調(diào)用此方法的線程上引發(fā) ThreadAbortException,以開(kāi)始終止此線程的過(guò)程。 調(diào)用此方法通常會(huì)終止線程。 |
Abort(Object) | 引發(fā)在其上調(diào)用的線程中的 ThreadAbortException以開(kāi)始處理終止線程,同時(shí)提供有關(guān)線程終止的異常信息。 調(diào)用此方法通常會(huì)終止線程。 |
Abort()
方法給線程注入 ThreadAbortException
異常,導(dǎo)致程序被終止。但是不一定可以終止線程。
2.5 線程的不確定性
線程的不確定性是指幾個(gè)并行運(yùn)行的線程,不確定在下一刻 CPU 時(shí)間片會(huì)分配給誰(shuí)(當(dāng)然,分配有優(yōu)先級(jí))。
對(duì)我們來(lái)說(shuō),多線程是同時(shí)運(yùn)行
的,但一般 CPU 沒(méi)有那么多核,不可能在同一時(shí)刻執(zhí)行所有的線程。CPU 會(huì)決定某個(gè)時(shí)刻將時(shí)間片分配給多個(gè)線程中的一個(gè)線程,這就出現(xiàn)了 CPU 的時(shí)間片分配調(diào)度。
執(zhí)行下面的代碼示例,你可以看到,兩個(gè)線程打印的順序是不確定的,而且每次運(yùn)行結(jié)果都不同。
CPU 有一套公式確定下一次時(shí)間片分配給誰(shuí),但是比較復(fù)雜,需要學(xué)習(xí)計(jì)算機(jī)組成原理和操作系統(tǒng)。
留著下次寫(xiě)文章再講。
static void Main(string[] args) { Thread thread1 = new Thread(Test1); Thread thread2 = new Thread(Test2); thread1.Start(); thread2.Start(); Console.ReadKey(); } public static void Test1() { for (int i = 0; i < 10; i++) { Console.WriteLine("Test1:" + i); } } public static void Test2() { for (int i = 0; i < 10; i++) { Console.WriteLine("Test2:" + i); } }
2.6 線程優(yōu)先級(jí)、前臺(tái)線程和后臺(tái)線程
Thread.Priority
屬性用于設(shè)置線程的優(yōu)先級(jí),Priority
是一個(gè) ThreadPriority 枚舉,其枚舉類型如下
枚舉 | 值 | 說(shuō)明 |
---|---|---|
AboveNormal | 3 | 可以將 安排在具有 Highest 優(yōu)先級(jí)的線程之后,在具有 Normal 優(yōu)先級(jí)的線程之前。 |
BelowNormal | 1 | 可以將 Thread 安排在具有 Normal 優(yōu)先級(jí)的線程之后,在具有 Lowest 優(yōu)先級(jí)的線程之前。 |
Highest | 4 | 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之前。 |
Lowest | 0 | 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之后。 |
Normal | 2 | 可以將 Thread 安排在具有 AboveNormal 優(yōu)先級(jí)的線程之后,在具有 BelowNormal 優(yōu)先級(jí)的線程之前。 默認(rèn)情況下,線程具有 Normal 優(yōu)先級(jí)。 |
優(yōu)先級(jí)排序:Highest
> AboveNormal
> Normal
> BelowNormal
> Lowest
。
Thread.IsBackgroundThread
可以設(shè)置線程是否為后臺(tái)線程。
前臺(tái)線程的優(yōu)先級(jí)大于后臺(tái)線程,并且程序需要等待所有前臺(tái)線程執(zhí)行完畢后才能關(guān)閉;而當(dāng)程序關(guān)閉是,無(wú)論后臺(tái)線程是否在執(zhí)行,都會(huì)強(qiáng)制退出。
2.7 自旋和休眠
當(dāng)線程處于進(jìn)入休眠狀態(tài)或解除休眠狀態(tài)時(shí),會(huì)發(fā)生上下文切換,這就帶來(lái)了昂貴的消耗。
而線程不斷運(yùn)行,就會(huì)消耗 CPU 時(shí)間,占用 CPU 資源。
對(duì)于過(guò)短的等待,應(yīng)該使用自旋(spin)方法,避免發(fā)生上下文切換;過(guò)長(zhǎng)的等待應(yīng)該使線程休眠,避免占用大量 CPU 時(shí)間。
我們可以使用最為熟知的 Sleep()
方法休眠線程。有很多同步線程的類型,也使用了休眠手段等待線程(已經(jīng)寫(xiě)好草稿啦)。
自旋的意思是,沒(méi)事找事做。
例如:
public static void Test(int n) { int num = 0; for (int i=0;i<n;i++) { num += 1; } }
通過(guò)做一些簡(jiǎn)單的運(yùn)算,來(lái)消耗時(shí)間,從而達(dá)到等待的目的。
C# 中有關(guān)于自旋的自旋鎖和 Thread.SpinWait();
方法,在后面的線程同步分類中會(huì)說(shuō)到自旋鎖。
Thread.SpinWait()
在極少數(shù)情況下,避免線程使用上下文切換很有用。其定義如下
public static void SpinWait(int iterations);
SpinWait 實(shí)質(zhì)上是(處理器)使用了非常緊密的循環(huán),并使用 iterations
參數(shù)指定的循環(huán)計(jì)數(shù)。 SpinWait 等待時(shí)間取決于處理器的速度。
SpinWait 無(wú)法使你準(zhǔn)確控制等待時(shí)間,主要是使用一些鎖時(shí)用到,例如 Monitor.Enter。
到此這篇關(guān)于C#多線程系列之線程的創(chuàng)建和生命周期的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- C#多線程系列之工作流實(shí)現(xiàn)
- C#多線程系列之任務(wù)基礎(chǔ)(三)
- C#多線程系列之任務(wù)基礎(chǔ)(二)
- C#多線程系列之任務(wù)基礎(chǔ)(一)
- C#多線程系列之線程池
- C#多線程系列之線程等待
- C#多線程系列之讀寫(xiě)鎖
- C#多線程系列之多階段并行線程
- C#多線程系列之線程完成數(shù)
- C#多線程系列之手動(dòng)線程通知
- C#多線程系列之線程通知
- C#多線程系列之資源池限制
- C#多線程系列之進(jìn)程同步Mutex類
- C#多線程系列之原子操作
- C#多線程系列之多線程鎖lock和Monitor
- C#多線程系列之a(chǎn)sync和await用法詳解
相關(guān)文章
利用FlubuCore用C#來(lái)寫(xiě)DevOps腳本的方法詳解
這篇文章主要介紹了利用FlubuCore用C#來(lái)寫(xiě)DevOps腳本的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07C# Winform 調(diào)用系統(tǒng)接口操作 INI 配置文件的代碼
封裝了一小段代碼, 調(diào)用系統(tǒng)接口, 操作配置文件. 一般用于 .ini 文件, 或者其它鍵值對(duì)格式的配置文件2011-05-05Unity 從UI中拖拽對(duì)象放置并拖動(dòng)效果 附demo
最近新接了個(gè)需求,要求模擬場(chǎng)景并生成3D對(duì)象,對(duì)象可以跟隨鼠標(biāo)移動(dòng)效果,今天小編把我實(shí)現(xiàn)的demo分享到腳本之家平臺(tái),對(duì)Unity UI拖拽相關(guān)知識(shí)感興趣的朋友跟隨小編一起學(xué)習(xí)吧2021-05-05C#類型轉(zhuǎn)換之自定義隱式轉(zhuǎn)換和顯式轉(zhuǎn)換
本文主要為大家介紹了一個(gè)新的類型轉(zhuǎn)換方法:通過(guò)自定義隱式轉(zhuǎn)換,把不一樣的數(shù)據(jù)類型反序列化為一樣的數(shù)據(jù)類型,需要的同學(xué)可以參考一下2022-03-03C# OpenCvSharp利用白平衡技術(shù)實(shí)現(xiàn)圖像修復(fù)功能
這篇文章主要為大家詳細(xì)介紹了C# OpenCvSharp如何利用白平衡技術(shù)實(shí)現(xiàn)圖像修復(fù)功能,文中的示例代碼講解詳細(xì),希望對(duì)大家有一定的幫助2024-02-02C#利用FluentFTP實(shí)現(xiàn)FTP上傳下載功能詳解
FTP作為日常工作學(xué)習(xí)中,非常重要的一個(gè)文件傳輸存儲(chǔ)空間,想必大家都非常的熟悉了,那么如何快速的實(shí)現(xiàn)文件的上傳下載功能呢,本文以一個(gè)簡(jiǎn)單的小例子,簡(jiǎn)述如何通過(guò)FluentFTP實(shí)現(xiàn)文件的上傳和下載功能2023-02-02