C#多線程基本使用小結(jié)
線程是并發(fā)編程的基礎(chǔ)概念之一。在現(xiàn)代應(yīng)用程序中,我們通常需要執(zhí)行多個任務(wù)并行處理,以提高性能。C# 提供了多種并發(fā)編程工具,如Thread
、Task
、異步編程和Parallel
等。
Thread 類
Thread
類是最基本的線程實現(xiàn)方法。使用Thread
類,我們可以創(chuàng)建并管理獨立的線程來執(zhí)行任務(wù)。
基本使用Thread
創(chuàng)建一個新的實例對象,將一個方法直接給Thread類,并使用實例對象啟動線程運行。
static void Test() { Console.WriteLine("Test事件開始執(zhí)行"); Thread.Sleep(1000); Console.WriteLine("Test事件睡眠之后打印數(shù)據(jù)"); Console.WriteLine("Test事件id: " + Thread.CurrentThread.ManagedThreadId); } static void Main(string[] args) { #region //主線程id Console.WriteLine("主線程id: " + Thread.CurrentThread.ManagedThreadId); #endregion #region //傳遞一個方法給線程,執(zhí)行方法 Thread t = new Thread(Test); t.Start(); #endregion }
線程數(shù)據(jù)傳輸
傳遞單個參數(shù)
Thread
提供了一個 Start(object parameter)
方法,可以在啟動線程時傳遞一個參數(shù)。
static int Downlod(object obj) { string str = obj as string; Console.WriteLine(str); } static void Main(string[] args) { Thread t1 = new Thread(Downlod); //這里傳遞的參數(shù)是字符串 t1.Start("數(shù)據(jù)傳遞方法執(zhí)行 ,數(shù)據(jù)傳遞方法id: "+Thread.CurrentThread.ManagedThreadId); }
這種方式適用于需要傳遞單個參數(shù)的情況。
使用類的實例方法傳遞多個參數(shù)
先new一個類的實例,給實例字段賦值,調(diào)用類的實例,通過實例調(diào)用方法,構(gòu)造函數(shù)接收參數(shù),然后在線程中調(diào)用該實例的方法。
static void Main(string[] args) { // 使用 DownloadTool 實例化并傳遞數(shù)據(jù) DownloadTool downloadTool = new DownloadTool("www.baidu.com", "這是下載鏈接哦"); Thread thread = new Thread(downloadTool.Download); thread.Start(); } public class DownloadTool { private string url; private string message; public DownloadTool(string url, string message) { this.url = url; this.message = message; } public void Download() { Console.WriteLine("下載鏈接: " + url); Console.WriteLine("提示信息: " + message); Console.WriteLine("Download 線程 ID: " + Thread.CurrentThread.ManagedThreadId); } }
當 thread
線程啟動時,它會執(zhí)行 downloadTool.Download()
方法,輸出傳遞的數(shù)據(jù)。
線程優(yōu)先級
在 C# 中,可以使用 Thread.Priority
屬性來設(shè)置線程的優(yōu)先級。線程優(yōu)先級決定了操作系統(tǒng)在多線程環(huán)境中調(diào)度線程的順序,但并不保證高優(yōu)先級的線程總是比低優(yōu)先級的線程更早或更頻繁地執(zhí)行。
線程優(yōu)先級級別
C# 提供了五個線程優(yōu)先級級別,定義在 ThreadPriority
枚舉中:
- Lowest:最低優(yōu)先級。操作系統(tǒng)盡可能少地調(diào)度這個優(yōu)先級的線程。
- BelowNormal:低于正常的優(yōu)先級。優(yōu)先級比 Normal 低,但高于 Lowest。
- Normal:默認優(yōu)先級,大多數(shù)線程默認的優(yōu)先級。適用于一般用途。
- AboveNormal:高于正常的優(yōu)先級。操作系統(tǒng)更傾向于調(diào)度這個優(yōu)先級的線程。
- Highest:最高優(yōu)先級。操作系統(tǒng)盡可能多地調(diào)度這個優(yōu)先級的線程。
internal class Program { static void A() { int i = 0; while (true) { i++; Console.WriteLine($"A 輸出第{i}"); Thread.Sleep(1000); } } static void B() { int i = 0; while (true) { i++; Console.WriteLine($"B 輸出第{i}"); Thread.Sleep(1000); } } static void Main(string[] args) { //在C#中,線程的優(yōu)先級可以通過Thread.Priority屬性來設(shè)置和獲取。 // Lowest: 線程的優(yōu)先級是最低的。在系統(tǒng)中存在其他活動線程時,此優(yōu)先級的線程很少得到執(zhí)行。 //BelowNormal: 線程的優(yōu)先級低于正常線程。 //Normal: 線程的優(yōu)先級是普通的,這是線程的默認優(yōu)先級。 //AboveNormal: 線程的優(yōu)先級高于正常線程。 //Highest: 線程的優(yōu)先級是最高的。此優(yōu)先級的線程會盡量優(yōu)先于其他所有優(yōu)先級的線程執(zhí)行。 Thread a = new Thread(A); Thread b = new Thread(B); a.Priority = ThreadPriority.Highest; a.Start(); b.Priority = ThreadPriority.Lowest; b.Start(); Console.WriteLine("按任意鍵停止線程..."); Console.ReadKey(); a.Join(); b.Join(); Console.WriteLine("線程已停止"); } }
A
被設(shè)置為最高優(yōu)先級ThreadPriority.Highest
。B
被設(shè)置為最低優(yōu)先級ThreadPriority.Lowest
。
注意事項
- 優(yōu)先級不是絕對控制:操作系統(tǒng)可能會忽略優(yōu)先級設(shè)置,特別是在資源有限的系統(tǒng)中。高優(yōu)先級線程不一定會一直執(zhí)行,也不能阻止低優(yōu)先級線程的執(zhí)行。
- 使用優(yōu)先級的適用場景:設(shè)置線程優(yōu)先級可能適用于實時系統(tǒng)(例如,某些任務(wù)需要優(yōu)先處理)。但是,大多數(shù)應(yīng)用程序通常可以使用默認的
Normal
優(yōu)先級。 - 避免使用過多的高優(yōu)先級線程:如果所有線程都被設(shè)置為
Highest
,系統(tǒng)的整體性能可能會下降,甚至導(dǎo)致線程爭用 CPU 資源的情況。 - CPU 密集型任務(wù):在 CPU 密集型任務(wù)中,優(yōu)先級可能會對性能產(chǎn)生較大影響,因為優(yōu)先級高的線程可能會占用更多的 CPU 時間。
線程優(yōu)先級的最佳實踐
- 默認使用
Normal
優(yōu)先級,除非有特殊原因。 - 避免濫用
Highest
優(yōu)先級,因為它會對系統(tǒng)資源產(chǎn)生影響。 - 在 I/O 密集型的線程中,優(yōu)先級通常不會有顯著差異,因為這些線程在等待 I/O 操作完成時,CPU 會調(diào)度其他線程。
通過合理設(shè)置線程優(yōu)先級,可以幫助操作系統(tǒng)更好地調(diào)度線程,以滿足應(yīng)用程序的需求。 但通常在 .NET 應(yīng)用程序中,多數(shù)情況下使用默認的 Normal
優(yōu)先級就足夠了。
線程池
線程池 (ThreadPool
) 是一種高效的管理和調(diào)度線程的方式。線程池自動管理線程的創(chuàng)建、重用和銷毀,從而減少了手動創(chuàng)建和管理線程的開銷。
為什么使用線程池
- 性能更高:線程池會重用現(xiàn)有的線程,減少了創(chuàng)建和銷毀線程的開銷。
- 自動管理:線程池會根據(jù)系統(tǒng)負載動態(tài)調(diào)整線程數(shù)量。
- 避免線程資源不足:線程池限制了同時運行的線程數(shù),避免了線程過多導(dǎo)致的資源耗盡問題。
基本使用 ThreadPool.QueueUserWorkItem
方法將任務(wù)排入線程池隊列。
using System; using System.Threading; class Program { static void Main(string[] args) { // 將任務(wù)排入線程池 ThreadPool.QueueUserWorkItem(DoWork, "任務(wù) 1"); ThreadPool.QueueUserWorkItem(DoWork, "任務(wù) 2"); Console.WriteLine("主線程完成"); Thread.Sleep(3000); // 等待線程池中的任務(wù)完成 } static void DoWork(object state) { string taskName = (string)state; Console.WriteLine($"{taskName} 開始執(zhí)行 - 線程ID: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); // 模擬耗時操作 Console.WriteLine($"{taskName} 執(zhí)行完成 - 線程ID: {Thread.CurrentThread.ManagedThreadId}"); } }
運行結(jié)果QueueUserWorkItem自動分配線程來執(zhí)行任務(wù)。
QueueUserWorkItem
方法將任務(wù)排入線程池。它接收一個委托(即方法)和一個可選的狀態(tài)對象(傳遞給方法的數(shù)據(jù))。
DoWork
方法接受一個參數(shù) state
,這是從 QueueUserWorkItem
傳遞的。
Thread.Sleep(3000)
確保主線程不會立即退出,使得線程池中的任務(wù)有機會完成。
使用帶返回值的線程池任務(wù)
C# 中的 ThreadPool
通常不直接支持返回值。如果需要獲得任務(wù)結(jié)果,可以使用 Task
,因為 Task
本質(zhì)上也是線程池的一部分。Task
更適合于帶返回值的異步操作。這里使用 Task.Run
來代替 ThreadPool
:
static void Main(string[] args) { //使用tesk多線程 int a= Task.Run(() => { int a = Dowload(); return a; }).Result; Task<int> task = Task<int>.Run(()=>{ int a = Dowload(); return a; }); //初始化一個CancellationTokenSource實例 CancellationTokenSource source = new CancellationTokenSource(); //task.Start(); task.Wait(1000); source.Cancel(); int result = task.Result; Console.WriteLine(result); Console.WriteLine($"tesk返回值{a}"); } static int Dowload() { int a = 0; for (int i = 0; i < 10; i++) { a= a + i + 1; } int? id= Task.CurrentId; Console.WriteLine("Current thread ID: " + id); return a; }
線程池的限制
- 任務(wù)運行時間過長:線程池中的線程本質(zhì)上是共享資源,如果某個任務(wù)運行時間太長,將會占用線程池中的線程,導(dǎo)致其他任務(wù)無法及時執(zhí)行。
- 不適合實時系統(tǒng):線程池中的任務(wù)調(diào)度是由系統(tǒng)管理的,無法保證精確的實時性。
- 有限的線程數(shù)量:在高并發(fā)場景中,如果線程池中的線程全部被占用,新的任務(wù)將會等待,直到有線程可用。
線程池總結(jié)
線程池是一種高效的并發(fā)處理方式,適合于大多數(shù)輕量級的后臺任務(wù)。在現(xiàn)代 C# 編程中,建議使用 Task
和 async/await
進行異步操作,因為它們能簡化代碼,并且使用底層的線程池來管理線程。如果需要精確控制線程的執(zhí)行,通常建議使用手動管理的 Thread
等。
線程鎖
使用多線程,在多線程編程中,如果多個線程同時訪問和修改共享資源(如全局變量、文件、數(shù)據(jù)庫等),可能會導(dǎo)致數(shù)據(jù)不一致或競爭條件。為了避免這種情況,多線程鎖通過控制對共享資源的訪問來保證線程安全性。
資源沖突示例:
static void Main(string[] args) { //調(diào)用方法循環(huán)創(chuàng)建新的線程來執(zhí)行方法 StateObject state = new StateObject(); for (int i = 0; i < 30; i++) { Thread thread = new Thread(state.ChangState); thread.Start(); } Console.ReadKey(); } //一個StateObject類 public class StateObject { private int state = 5; //里面有個狀態(tài)改變方法,當狀態(tài)等于5的時候進入到方法中,然后state+1 打印的應(yīng)該是6 和線程id public void ChangState() { if (state == 5) { state++; Console.WriteLine($"state:{state} 線程id:" + Thread.CurrentThread.ManagedThreadId); } state = 5; } }
運行結(jié)果:
因為資源沖突的原因,有一些線程執(zhí)行的時候,可能另外一個線程沒有執(zhí)行完,另外一個線程就已經(jīng)進入到方法里面了。因為有修改的操作導(dǎo)致State狀態(tài)混亂,資源沖突。這時候我們就需要一個東西來維護,讓線程執(zhí)行指定方法時一個一個的執(zhí)行,不會沖突。
簡單使用lock鎖
1.創(chuàng)建一個private readonly object lockObject = new object(); // 鎖對象
2.使用lock (lockObject){
//業(yè)務(wù)代碼,執(zhí)行到有修改的操作的代碼
} // 使用鎖對象來確保線程安全
static void Main(string[] args) { StateObject state = new StateObject(); for (int i = 0; i < 100; i++) { Thread thread = new Thread(state.ChangState); thread.Start(); } Console.ReadKey(); } public class StateObject { private object _lock = new object(); private int state = 5; public void ChangState() { //使用鎖保證每次執(zhí)行方法的都是一個線程,防止資源沖突 lock (_lock) { if (state == 5) { state++; Console.WriteLine($"state:{state} 線程id:" + Thread.CurrentThread.ManagedThreadId); } state = 5; } } }
這樣運行起來就能把每個線程操作隔離開,保證state的狀態(tài)不會沖突。
為什么要創(chuàng)建一個 object
對象作為鎖?
- 專用性:創(chuàng)建一個專用的鎖對象(如
private readonly object lockObject = new object();
),可以確保鎖定的是特定的同步邏輯,而不是其他對象。這有助于避免意外的鎖沖突或死鎖。 - 避免使用其他共享對象:雖然可以使用任意的引用類型對象作為鎖對象(包括
this
或Type
對象),但這可能會帶來不必要的風(fēng)險,尤其是在public
方法或?qū)ο笾?,這樣可能會導(dǎo)致意外的鎖定沖突。
常見問題死鎖:
鎖并不是萬能,也不是有鎖就是最好,要看情況使用,鎖也會產(chǎn)生問題,常見的問題就是死鎖等問題。
演示死鎖:
thread1方法1 的鎖里面嵌套鎖住了thread2的鎖,thread2方法2的鎖里面嵌套鎖住了thread1的鎖,這種鎖與鎖嵌套使用,就是容易出問題。導(dǎo)致線程鎖死程序無法動彈。
static void Main(string[] args) { DeadlockExample deadlockExample = new DeadlockExample(); Thread t1 = new Thread(deadlockExample.Thread1); Thread t2 = new Thread(deadlockExample.Thread2); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadKey(); } } public class DeadlockExample { private static object lock1 = new object(); private static object lock2 = new object(); public void Thread1() { lock (lock1) { Console.WriteLine("線程1:已獲取鎖1,正在等待鎖2。。。"); Thread.Sleep(100); // 模擬某些工作 lock (lock2) { Console.WriteLine("線程1:獲得鎖2"); } } } public void Thread2() { lock (lock2) { Console.WriteLine("線程2:已獲取鎖2,正在等待鎖1。。。"); Thread.Sleep(100); // 模擬某些工作 lock (lock1) { Console.WriteLine("線程2:獲得鎖1"); } } } }
運行結(jié)果:
死鎖發(fā)生在兩個或多個線程相互等待對方持有的資源,導(dǎo)致所有線程都無法繼續(xù)執(zhí)行。
總結(jié):
多線程鎖在 C# 中主要用于解決以下問題:
- 競態(tài)條件:通過鎖機制防止多個線程同時訪問和修改共享資源,確保數(shù)據(jù)一致性。
- 死鎖:防止多個線程相互等待資源,通過鎖的順序或者避免嵌套鎖來解決。
- 資源饑餓:確保每個線程都能獲取資源,使用
Monitor.TryEnter
等機制防止無限等待。 - 讀寫鎖:允許多個線程并發(fā)讀取資源,但寫入時互斥,適合讀多寫少的場景。
C# 提供了多種鎖機制,開發(fā)者可以根據(jù)應(yīng)用場景選擇合適的鎖類型。
如果不想使用 lock
關(guān)鍵字,C# 還提供了其他鎖機制,比如 Mutex
、Semaphore
、Monitor
等
到此這篇關(guān)于C#多線程基本使用和探討的文章就介紹到這了,更多相關(guān)C#多線程使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用序列化實現(xiàn)List<T> 實例的深復(fù)制(推薦)
下面小編就為大家?guī)硪黄眯蛄谢瘜崿F(xiàn)List<T> 實例的深復(fù)制(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02c#中利用委托反射將DataTable轉(zhuǎn)換為實體集的代碼
c#中利用委托反射將DataTable轉(zhuǎn)換為實體集的代碼,需要的朋友可以參考下2012-10-10