您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“C#線程的創(chuàng)建和生命周期實(shí)例分析”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
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è)簡單的示例:
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
一般認(rèn)為,線程有五種狀態(tài):
新建(new 對象) 、就緒(等待CPU調(diào)度)、運(yùn)行(CPU正在運(yùn)行)、阻塞(等待阻塞、同步阻塞等)、死亡(對象釋放)。
理論的東西不說太多,直接擼代碼。
新建線程簡直滾瓜爛熟,無非 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ù)來舉例,使用這四個(gè)構(gòu)造函數(shù)呢?
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); }
此種方法不需要作為參數(shù)傳遞,各個(gè)線程共享堆棧。
優(yōu)點(diǎn)是不需要裝箱拆箱,多線程可以共享空間;缺點(diǎn)是變量是大家都可以訪問,此種方式在多線程競價(jià)時(shí),可能會(huì)導(dǎo)致多種問題(可以加鎖解決)。
下面使用兩個(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); // 本身對象的其它成員 } public static void OneTest2() { Console.WriteLine("新的線程已經(jīng)啟動(dòng)"); Console.WriteLine(B); // 全局靜態(tài)變量 } }
原理是 Thread 的構(gòu)造函數(shù) public Thread(ThreadStart start);
,ThreadStart
是一個(gè)委托,其定義如下
public delegate void ThreadStart();
使用委托的話,可以這樣寫
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)麻煩,不過我們可以使用 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)"); }
提示:如果需要處理的算法比較簡單的話,可以直接寫進(jìn)委托中,不需要另外寫方法啦。
可以看到,C# 是多么的方便。
Thread.Sleep()
方法可以將當(dāng)前線程掛起一段時(shí)間,Thread.Join()
方法可以阻塞當(dāng)前線程一直等待另一個(gè)線程運(yùn)行至結(jié)束。
在等待線程 Sleep()
或 Join()
的過程中,線程是阻塞的(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}:小弟弟開始玩游戲"); 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 + "開始打游戲"); 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)簡單的線程同步,即一個(gè)線程等待另一個(gè)線程完成。
ThreadState
是一個(gè)枚舉,記錄了線程的狀態(tài),我們可以從中判斷線程的生命周期和健康情況。
其枚舉如下:
枚舉 | 值 | 說明 |
---|---|---|
Initialized | 0 | 此狀態(tài)指示線程已初始化但尚未啟動(dòng)。 |
Ready | 1 | 此狀態(tài)指示線程因無可用的處理器而等待使用處理器。 線程準(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í)行堆棧從磁盤中分頁。 |
Unknown | 7 | 線程的狀態(tài)未知。 |
Wait | 5 | 此狀態(tài)指示線程尚未準(zhǔn)備好使用處理器,因?yàn)樗诘却鈬僮魍瓿苫虻却Y源釋放。 當(dāng)線程就緒后,將對其進(jìn)行重排。 |
但是里面有很多枚舉類型是沒有用處的,我們可以使用一個(gè)這樣的方法來獲取更加有用的信息:
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}:小弟弟開始玩游戲"); 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(); }
代碼看著比較亂,請復(fù)制到項(xiàng)目中運(yùn)行一下。
輸出示例:
2020/4/11 11:01:48:大家在吃飯,吃完飯后要帶小弟弟逛街 吃完飯了 2020/4/11 11:01:48:小弟弟開始玩游戲 弟弟在干嘛?(線程狀態(tài)):Unstarted 弟弟在干嘛?(線程狀態(tài)):Running 不管他,大姐姐化妝先 小弟弟開始打游戲 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),即未開始(就緒)、阻塞、運(yùn)行中、死亡。
.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,不做示例。
方法 | 說明 |
---|---|
Abort() | 在調(diào)用此方法的線程上引發(fā) ThreadAbortException,以開始終止此線程的過程。 調(diào)用此方法通常會(huì)終止線程。 |
Abort(Object) | 引發(fā)在其上調(diào)用的線程中的 ThreadAbortException以開始處理終止線程,同時(shí)提供有關(guān)線程終止的異常信息。 調(diào)用此方法通常會(huì)終止線程。 |
Abort()
方法給線程注入 ThreadAbortException
異常,導(dǎo)致程序被終止。但是不一定可以終止線程。
線程的不確定性是指幾個(gè)并行運(yùn)行的線程,不確定在下一刻 CPU 時(shí)間片會(huì)分配給誰(當(dāng)然,分配有優(yōu)先級(jí))。
對我們來說,多線程是同時(shí)運(yùn)行
的,但一般 CPU 沒有那么多核,不可能在同一時(shí)刻執(zhí)行所有的線程。CPU 會(huì)決定某個(gè)時(shí)刻將時(shí)間片分配給多個(gè)線程中的一個(gè)線程,這就出現(xiàn)了 CPU 的時(shí)間片分配調(diào)度。
執(zhí)行下面的代碼示例,你可以看到,兩個(gè)線程打印的順序是不確定的,而且每次運(yùn)行結(jié)果都不同。
CPU 有一套公式確定下一次時(shí)間片分配給誰,但是比較復(fù)雜,需要學(xué)習(xí)計(jì)算機(jī)組成原理和操作系統(tǒng)。
留著下次寫文章再講。
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); } }
Thread.Priority
屬性用于設(shè)置線程的優(yōu)先級(jí),Priority
是一個(gè) ThreadPriority 枚舉,其枚舉類型如下
枚舉 | 值 | 說明 |
---|---|---|
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)閉是,無論后臺(tái)線程是否在執(zhí)行,都會(huì)強(qiáng)制退出。
當(dāng)線程處于進(jìn)入休眠狀態(tài)或解除休眠狀態(tài)時(shí),會(huì)發(fā)生上下文切換,這就帶來了昂貴的消耗。
而線程不斷運(yùn)行,就會(huì)消耗 CPU 時(shí)間,占用 CPU 資源。
對于過短的等待,應(yīng)該使用自旋(spin)方法,避免發(fā)生上下文切換;過長的等待應(yīng)該使線程休眠,避免占用大量 CPU 時(shí)間。
我們可以使用最為熟知的 Sleep()
方法休眠線程。有很多同步線程的類型,也使用了休眠手段等待線程(已經(jīng)寫好草稿啦)。
自旋的意思是,沒事找事做。
例如:
public static void Test(int n) { int num = 0; for (int i=0;i<n;i++) { num += 1; } }
通過做一些簡單的運(yùn)算,來消耗時(shí)間,從而達(dá)到等待的目的。
C# 中有關(guān)于自旋的自旋鎖和 Thread.SpinWait();
方法,在后面的線程同步分類中會(huì)說到自旋鎖。
Thread.SpinWait()
在極少數(shù)情況下,避免線程使用上下文切換很有用。其定義如下
public static void SpinWait(int iterations);
SpinWait 實(shí)質(zhì)上是(處理器)使用了非常緊密的循環(huán),并使用 iterations
參數(shù)指定的循環(huán)計(jì)數(shù)。 SpinWait 等待時(shí)間取決于處理器的速度。
SpinWait 無法使你準(zhǔn)確控制等待時(shí)間,主要是使用一些鎖時(shí)用到,例如 Monitor.Enter。
“C#線程的創(chuàng)建和生命周期實(shí)例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。