并發(fā)編程 - 初識線程
01、什么是線程?
要深刻理解什么是線程,就需要了解計算機的發(fā)展史,需要了解多任務概念,需要了解進程概念,然后才是線程概念。因為我們主要還是講解線程,因此這里就不進行展開說其他概念了,有興趣的可以自行了解下。
簡單來說,線程就是操作系統(tǒng)中能夠單獨執(zhí)行任務的最小單元。

對于大多數(shù)編程語言來說,都有一個或者類似的功能的Main()方法,而該方法中的所有代碼也都是按照順序一行一行的執(zhí)行,如果要想執(zhí)行下一行代碼那么就必須等待上一行代碼執(zhí)行完成。而線程作為能夠單獨執(zhí)行任務的最小單元,因此線程可以使得應用程序的一部分獨立于另外一部分而單獨運行,這也就意味著我們可以改變程序的常規(guī)代碼執(zhí)行順序,從而達到更復雜的程序控制。
對于一個應用程序來說,要想正常運行至少要有一個線程,通常為主線程,也就是上文中提到的Main()方法,當調(diào)用此方法時系統(tǒng)就會自動創(chuàng)建一個主線程。
02、后臺線程和前臺線程
線程可以分為前臺線程和后臺線程,兩者基本完全相同,唯一區(qū)別是前臺線程可以在托管執(zhí)行環(huán)境中一直運行,而后臺線程不可以。簡單來說就是當進程中所有前臺進程都停止后,系統(tǒng)會自動停止并關閉該進程內(nèi)的所有后臺線程。
在C#中可以通過Thread.IsBackground查看當前線程類型,也可以通過該屬性修改線程類型。默認情況下,通過Thread對象新建并啟動的所有線程都是前臺線程。
看如下簡單示例:
public static void CreateThread()
{
Console.WriteLine($"主線程 是否為后臺線程:{Thread.CurrentThread.IsBackground}");
var thread1 = new Thread(()=> Console.WriteLine("Hello World"));
Console.WriteLine($" 線程1 默認為后臺線程:{thread1.IsBackground}");
thread1.IsBackground = true;
Console.WriteLine($" 線程1 設置為后臺線程:{thread1.IsBackground}");
thread1.Start();
}
執(zhí)行效果如下:

03、線程的優(yōu)先級
線程作為操作系統(tǒng)中能夠單獨執(zhí)行任務的最小單元,那么當一個進程中有多個線程時,應該先執(zhí)行那個線程呢?因此線程需要一個標記其執(zhí)行優(yōu)先級的屬性。
在C#中Thread可以通過Priority來設置線程的優(yōu)先級,告訴系統(tǒng)應該先執(zhí)行誰。ThreadPriority有以下5種類型:
- Lowest: 最低優(yōu)先級,在所有優(yōu)先級中最低,在所有線程中可位于最后執(zhí)行。
- BelowNormal: 低于正常優(yōu)先級,在Normal優(yōu)先級之后,在Lowest優(yōu)先級之前。
- Normal: 默認優(yōu)先級,線程默認的優(yōu)先級
- AboveNormal: 高于正常優(yōu)先級,在Highest優(yōu)先級的線程之后,在Normal優(yōu)先級之前。
- Highest: 最高優(yōu)先級,在所有優(yōu)先級中最高,在所有線程中可優(yōu)先執(zhí)行。
下面我們做個簡單的測試,用來驗證優(yōu)先級不同的導致差異。
class ThreadPriorityTest
{
//是否執(zhí)行,確保一個線程修改此值后,其他線程立刻查看到最新值
static volatile bool isRun = true;
//確保每個線程都有獨立的副本存儲計數(shù)統(tǒng)計值
[ThreadStatic]
static long threadCount;
//停止運行
public void Stop()
{
isRun = false;
}
//打印線程名稱對應優(yōu)先級以及計數(shù)總數(shù)
public void Print()
{
threadCount = 0;
while (isRun)
{
threadCount++;
}
Console.WriteLine($"{Thread.CurrentThread.Name} 優(yōu)先級為{Thread.CurrentThread.Priority,8} 總執(zhí)行計數(shù)為:{threadCount,-13:N0}");
}
}
public static void PriorityTest()
{
var threadPriorityTest = new ThreadPriorityTest();
//創(chuàng)建3個線程,并設置優(yōu)先級
var thread1 = new Thread(threadPriorityTest.Print)
{
Name = "線程1"
};
var thread2 = new Thread(threadPriorityTest.Print)
{
Name = "線程2",
Priority = ThreadPriority.Lowest
};
var thread3 = new Thread(threadPriorityTest.Print)
{
Name = "線程3",
Priority = ThreadPriority.Highest
};
//啟動3個線程
thread1.Start();
thread2.Start();
thread3.Start();
//休眠3秒
Thread.Sleep(10000);
//停止運行
threadPriorityTest.Stop();
//等待所有線程完成
thread1.Join();
thread2.Join();
thread3.Join();
}
執(zhí)行效果如下:

可以發(fā)現(xiàn)優(yōu)先級越高,其執(zhí)行計數(shù)值越大。
其中需要注意的是volatile和ThreadStatic的用法。
在這個多線程示例中我們需要準確的統(tǒng)計不同的線程執(zhí)行計數(shù),因此正常來說可能需要設置多個變量用來對應存儲各自線程的統(tǒng)計計數(shù),很顯然這樣會導致代碼臃腫。因此我們選用了另一種辦法,使用ThreadStatic標記一個字段,使得該字段對每個線程都有獨立的副本。這樣可以做到線程之間不會共享這個字段的值,同時還可以做到多個線程只用這一個字段。
另外對于多線程共享的變量,很可能因為CPU緩存導致多個線程共享的變量不一致問題,因此通過volatile告訴編譯器和運行時每次訪問該字段時都要直接從內(nèi)存中讀取最新值,以此來保證線程之間的可見性。
04、線程的生命周期
當一個線程被創(chuàng)建后,會經(jīng)歷多個狀態(tài),包括未啟動、已啟動、執(zhí)行、睡眠、掛起等十個狀態(tài),同時Thread類也提供了一些方法,用來控制當前線程狀態(tài),比如啟動、停止、恢復、中止、掛起以及等待線程等方法。
我們先來看看線程具體有哪些狀態(tài):

- Running(運行)—— 線程已啟動,而且沒有停止;
- StopRequested(請求停止) —— 請求停止線程;
- SuspendRequested(請求掛起) —— 請求線程掛起;
- Background(后臺) —— 線程在后臺執(zhí)行;
- Unstarted(未啟動) —— 還沒有在線程上調(diào)用 Start()方法;
- Stopped(停止) —— 線程已完成了其所有的指令,而且已經(jīng)停止;
- WaitSleepJoin(等待睡眠連接) —— 通過調(diào)用 Wait()、Sleep()或 Join()方法,來暫停線程;
- Suspended(掛起) —— 線程處于掛起狀態(tài);
- AbortRequested(請求中止) —— Abort()方法已調(diào)用,但是線程還沒有收到試圖終止自己的 System.Threading.ThreadAbortexception,也就是說,線程還沒有停止但不久就會停止;
- Aborted****(中止) —— 線程處于停止狀態(tài),但不一定已執(zhí)行完畢;
下面我們再來看看線程的常用方法。
- Start(): 啟動線程,使其狀態(tài)變更為Running。
- Sleep(): 把正在運行的線程暫停一段時間后自動恢復,線程狀態(tài)保持活躍。
- Suspend():[已棄用]暫停當前線程的執(zhí)行,直到調(diào)用 Thread.Resume 顯式恢復。
- Resume():[已棄用]恢復一個已被暫停的線程。
- Interrupt(): 中斷處于 WaitSleepJoin 線程狀態(tài)的線程。
- Join(): 阻塞調(diào)用線程,直到某個線程終止時為止。
- Abort():[已棄用]終止當前線程。

通過源碼可以看到Resume和Suspend方法被棄用的原因。這是因為它們有很多問題和缺陷。使用它可能會導致程序的不穩(wěn)定、死鎖或者資源競爭問題。因此,它已經(jīng)被標記為廢棄,不推薦再使用。
在多線程編程中,通常可以通過合理的同步機制來控制線程的執(zhí)行。比如,使用上述的 Monitor、Mutex、Event 和 Semaphore 來協(xié)調(diào)多個線程的行為,確保資源訪問的安全和正確性。
注:測試方法代碼以及示例源碼都已經(jīng)上傳至代碼庫,有興趣的可以看看。https://gitee.com/hugogoos/Planner

浙公網(wǎng)安備 33010602011771號