C#多線程開發詳解
- 持續更新中。。。。。
- 一、為什么要使用多線程開發
- 1.提高性能
- 2.響應性
- 3.資源利用
- 4.任務分解
- 5.并行計算
- 6.實時處理
- 二、多線程開發缺點
- 1.競態條件
- 2.死鎖和饑餓
- 3.調試復雜性
- 4.上下文切換開銷
- 5.線程安全性
- 三、多線程開發涉及的相關概念
- 常用概念
- (1)lock
- (2)查看當前工作線程信息
- (3)主線程、前臺線程、后臺線程
- 1.Thread(線程)
- (1)創建線程
- (2) 線程同步
- (3)線程異步
- 2.ThreadPool(線程池)
- 3.Task(任務)
- (1)Task與Thead的關系
- 4.Task Parallel Library (TPL)(任務并行庫)
- 5.Async/Await(異步/等待)
- 6.Monitor(監視器)
- 7.Semaphore(信號量)
- 8.SemaphoreSlim
- 9.AutoResetEvent(自動復位事件)
- 10.ManualResetEvent(手動復位事件)
- 11.CancellationToken(取消標記)
- 12.volatile(易失性修飾符)
- 13.Mutex(互斥鎖)
- 14.ReaderWriterLock(讀寫鎖)
- 15.ReaderWriterLockSlim(輕量級讀寫鎖)
- 16.SpinLock
- 17.SpinWait
- 18.Barrier(屏障)
- 四、多線程的異常捕獲問題
持續更新中。。。。。
一、為什么要使用多線程開發
1.提高性能
多線程允許程序同時執行多個任務,從而有效利用多核處理器,加快程序的執行速度。特別是在需要處理大量計算、I/O 操作或并行任務的應用中,多線程可以顯著提高性能。
2.響應性
多線程使應用能夠同時處理多個用戶請求或事件,提高了應用的響應性。例如,多線程可以保持用戶界面的響應,即使在執行長時間操作時也能讓用戶繼續交互。
3.資源利用
多線程可以更有效地利用系統資源,如內存和網絡連接。這對于高并發服務器、網絡應用和數據處理任務特別有用。
4.任務分解
將復雜任務分解為多個小任務,每個任務在不同的線程中執行,可以簡化問題并提高可維護性。
5.并行計算
多線程可以用于并行計算,例如在科學計算、數據分析和圖像處理領域。這有助于加速大規模計算。
6.實時處理
在實時系統中,多線程可以保證任務在規定的時間內完成,從而滿足對時間敏感性的需求。
二、多線程開發缺點
1.競態條件
多線程可能會導致競態條件,即多個線程競爭訪問共享資源,可能導致數據不一致性和錯誤。
2.死鎖和饑餓
不正確的線程同步可能導致死鎖(多個線程無法繼續執行)或饑餓(某些線程無法獲取所需資源)問題。
線程1,2啟動,分別占用鎖lock1,lock2。之后線程1請求lock2,但是線程2已經占用lock2,線程1無法繼續執行,進入等待。線程2請求lock1,但是線程1已經占用lock1,線程2無法繼續執行,進入等待。這里陷入死鎖,線程1,線程2,都在等待對方釋放鎖來給自己使用,程序一直無法運行,一直在等待中。
using System;
using System.Threading;class DeadlockExample
{static object lock1 = new object();static object lock2 = new object();static void Main(){Thread thread1 = new Thread(Method1);Thread thread2 = new Thread(Method2);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine("Main thread finished.");}static void Method1(){lock (lock1){Console.WriteLine("Method1 acquired lock1.");Thread.Sleep(1000);Console.WriteLine("Method1 trying to acquire lock2.");lock (lock2){Console.WriteLine("Method1 acquired lock2.");}}}static void Method2(){lock (lock2){Console.WriteLine("Method2 acquired lock2.");Thread.Sleep(1000);Console.WriteLine("Method2 trying to acquire lock1.");lock (lock1){Console.WriteLine("Method2 acquired lock1.");}}}
}
3.調試復雜性
多線程程序的調試和錯誤跟蹤可能會更加復雜,因為線程間的交互和排錯可能變得更難。
4.上下文切換開銷
上下文切換(Context Switching)是多線程環境中的一種操作,指的是在一個 CPU 核心上切換正在執行的線程,從當前線程的執行上下文(包括寄存器狀態、程序計數器等)切換到另一個線程的執行上下文, 線程的切換需要額外的開銷,因此在某些情況下,過多的線程可能會導致性能下降。
- 當一個線程的時間片(時間片輪轉調度算法)用完,操作系統需要掛起該線程并切換到另一個線程。
- 當一個線程主動放棄 CPU,例如通過調用 Thread.Sleep()、Thread.Yield() 或等待某個事件時
3.當一個線程被高優先級的線程搶占
上下文切換的過程涉及以下步驟:
- 保存當前線程的上下文: 操作系統將當前線程的寄存器狀態、程序計數器等信息保存到該線程的內存空間中,以便稍后能夠恢復該線程的執行
2.恢復目標線程的上下文: 操作系統從目標線程的內存空間中恢復寄存器狀態、程序計數器等信息,準備讓目標線程繼續執行。- 切換內核堆棧: 每個線程都有自己的內核堆棧,上下文切換時,操作系統會切換內核堆棧,以確保線程的隔離性。
上下文切換開銷指的是從一個線程切換到另一個線程的過程中所涉及的時間和資源開銷。這些開銷主要包括以下幾個方面:
- 寄存器保存和恢復: 當線程切換時,操作系統需要保存當前線程的寄存器狀態,然后恢復目標線程的寄存器狀態。這涉及到大量的數據拷貝和計算。
2.內存訪問: 上下文切換過程中需要頻繁訪問內存,包括將寄存器狀態和其他上下文信息寫入內存,以及從內存中讀取目標線程的上下文信息。
3.調度開銷: 操作系統需要決定要切換到哪個線程,這涉及到調度算法的開銷,包括選擇合適的線程并進行必要的線程隊列操作。
4.TLB(Translation Lookaside Buffer)失效: 當線程切換時,虛擬內存的映射可能會發生變化,導致 TLB 緩存失效,從而增加了內存訪問的開銷。
上下文切換開銷會影響系統的整體性能,特別是在高并發、頻繁切換的情況下。因此,在設計多線程應用程序時,需要考慮如何減少上下文切換的發生,以提高程序的執行效率。一些方法包括:
- 使用線程池:線程池可以減少線程的創建和銷毀,從而減少上下文切換的頻率。
- 合理設置線程數量:避免創建過多線程,以減少不必要的上下文切換。
3.使用異步編程模型:使用異步操作和任務可以減少線程的使用,從而減少上下文切換。
5.線程安全性
多線程編程需要謹慎處理線程安全性,以避免數據競爭和共享資源的沖突。
三、多線程開發涉及的相關概念
常用概念
(1)lock
在 C# 中,lock 關鍵字用于實現線程同步,以確保在多線程環境中對共享資源的訪問是安全的。lock 關鍵字會創建一個互斥鎖(也稱為監視器鎖),只有一個線程可以獲得該鎖,從而確保在同一時間只有一個線程能夠執行被 lock 包圍的代碼塊。
lock (lockObject)
{// 在這里執行需要同步的代碼
}
其中,lockObject 是一個用于同步的對象。它可以是任何引用類型的對象,但通常是一個專門用于同步的對象。多個線程可以共享同一個 lockObject,并且只有一個線程能夠獲得鎖并執行被 lock 包圍的代碼塊。
class Program
{static readonly object lockObject = new object(); // 同步對象static void Main(string[] args){for (int i = 0; i < 5; i++){ThreadStart start = () =>{lock (lockObject){Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is in the critical section.");Thread.Sleep(1000);Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} has exited the critical section.");}};Thread thread = new Thread(start);thread.Start();}Console.ReadKey();}
}
(2)查看當前工作線程信息
可以使用 Thread.CurrentThread 屬性來獲取當前正在執行的線程的信息。這個屬性返回一個表示當前線程的 Thread 對象,你可以使用它來查詢線程的各種屬性和狀態。
Thread 類還提供了 Priority 屬性,允許你設置線程的優先級。然而,操作系統不一定會完全遵循線程的優先級,這取決于操作系統的調度機制。
線程可以分為前臺線程和后臺線程。前臺線程是主線程的一部分,如果所有前臺線程都完成,程序將終止。后臺線程是在后臺運行的線程,如果所有前臺線程都完成,程序會立即終止,不會等待后臺線程完成。
using System;
using System.Threading;class Program
{static void Main(){Thread currentThread = Thread.CurrentThread;Console.WriteLine($"Thread ID: {currentThread.ManagedThreadId}");Console.WriteLine($"Thread Name: {currentThread.Name}");Console.WriteLine($"Is Thread Background: {currentThread.IsBackground}");Console.WriteLine($"Thread Priority: {currentThread.Priority}");Console.WriteLine($"Thread State: {currentThread.ThreadState}");}
}
(3)主線程、前臺線程、后臺線程
主線程(Main Thread),它是程序的入口點,并且在程序啟動時自動創建。主線程負責啟動其他線程,并且通常是其他線程的父線程,但并不是所有線程都是主線程的子線程。
線程之間沒有嚴格的父子關系。主線程和其他線程之間通常是平等的,沒有直接的父子關系。但是,你可以通過編程來模擬一種線程間的層次關系,使得某些線程在邏輯上看起來是其他線程的子線程。這通常涉及線程的創建、協調和通信
以下是一個示例,演示了如何通過邏輯上的組織來模擬一種主線程和子線程的關系:
using System;
using System.Threading;class Program
{static void Main(){Console.WriteLine("Main thread starts.");Thread parentThread = new Thread(ParentThreadMethod);parentThread.Start();parentThread.Join();Console.WriteLine("Main thread ends.");}static void ParentThreadMethod(){Console.WriteLine("Parent thread starts.");Thread childThread = new Thread(ChildThreadMethod);childThread.Start();childThread.Join();Console.WriteLine("Parent thread ends.");}static void ChildThreadMethod(){Console.WriteLine("Child thread starts.");Thread.Sleep(2000);Console.WriteLine("Child thread ends.");}
}
前臺線程(Foreground Threads):
這些線程是由主線程或其他前臺線程創建的,它們的生命周期獨立于主線程,但它們不是主線程的子線程。前臺線程與主線程之間的關系是平級的。當所有前臺線程都執行完畢時,程序才會退出,無論主線程是否結束。
- 生命周期:
前臺線程的生命周期不受其他線程的影響。即使主線程退出,前臺線程仍然可以繼續執行,直到完成。- 程序退出:
如果程序中還有前臺線程在運行,主程序將等待所有前臺線程完成后才會退出。主線程也是前臺線程,如果主線程退出,會等待其他前臺線程完成后再退出。- 影響程序:
前臺線程會阻塞程序的退出,直到所有前臺線程完成。這可能會影響程序的退出速度。- 默認類型:
== 通過 new Thread(…) 創建的線程默認是前臺線程。==
后臺線程(Background Threads):
這些線程也是由主線程或其他前臺線程創建的,它們同樣是平級的,不是主線程的子線程。后臺線程與主線程之間的關系也是平級的。當所有前臺線程結束,程序會退出,同時會終止所有后臺線程,不管后臺線程是否執行完畢。
- 生命周期:
后臺線程的生命周期受到主線程的影響。如果所有前臺線程(包括主線程)都已經完成,程序會立即退出,同時終止后臺線程,不管后臺線程是否執行完畢。- 程序退出:
如果程序中只剩下后臺線程在運行,即使主線程結束,程序也會立即退出,不會等待后臺線程完成。- 影響程序:
后臺線程不會阻塞程序的退出,它們對程序的退出速度沒有影響。- 設置后臺線程:
可以通過設置線程的 IsBackground 屬性為 true 將線程設置為后臺線程。通過 Thread 類創建的線程可以使用這個屬性進行設置。
使用場景:
- 前臺線程通常用于執行一些關鍵任務,確保這些任務的完成。例如,在主線程需要等待其他線程的結果時,可以使用前臺線程。
- 后臺線程通常用于執行一些非關鍵性的任務,如日志記錄、監控等。它們不會阻止程序的退出,適用于在程序退出時不需要保證任務完全執行的情況。
錯誤使用后臺線程,可能引起資源泄露或意外行為
- 資源泄露:
如果后臺線程在程序退出時還在執行,可能會導致資源無法正確釋放。例如,如果后臺線程打開了文件、網絡連接或其他資源,但程序退出時這些資源沒有被正確關閉,就會發生資源泄露。- 不完整的操作:
如果后臺線程執行一些需要完整執行的操作,例如數據的寫入、狀態的更新等,但程序退出時這些操作未完成,可能會導致數據不一致或損壞。- 異常處理:
后臺線程的異常不會被捕獲并傳播到主線程,可能會導致未處理的異常,影響程序的穩定性。
4.線程同步:
在程序退出時,后臺線程可能還在等待某些同步操作完成,但這些操作可能無法在后臺線程終止之前完成,可能會導致死鎖或其他線程同步問題。
1.Thread(線程)
表示一個執行線程,用于并行執行代碼。可以使用 Thread 類來創建和管理線程。線程是執行程序的最小單位,多線程編程允許程序同時執行多個任務,從而提高性能和響應性。
Thread 類是 C# 中用于線程操作的基礎類之一。然而,對于更高級的線程編程需求,你可能會使用 Task、ThreadPool、異步編程模型等更高級的機制,以便更好地管理和協調多線程操作。
Thead常用方法
- Start(): 啟動線程,使其開始執行指定的方法。
- Join(): 阻塞當前線程,直到目標線程完成。
- Abort(): 強制終止線程的執行。不建議使用,因為可能導致資源泄漏或不穩定的狀態。
- Sleep(int millisecondsTimeout): 使當前線程休眠指定的毫秒數。
- IsAlive(): 返回一個布爾值,指示線程是否處于活動狀態。
- Interrupt(): 中斷線程,引發一個 ThreadInterruptedException 異常。
- Suspend() 和 Resume(): 已過時,不推薦使用。用于暫停和恢復線程的執行。
- GetDomain() 和 GetDomainID(): 獲取線程所屬的應用程序域和域標識符。
- SetApartmentState(ApartmentState state): 設置線程的單元狀態,用于控制線程的COM互操作行為。
- GetCurrentThreadId() 和 GetDomainID(): 獲取當前線程的唯一標識符。
- Interrupt(): 中斷線程的等待狀態,引發 ThreadInterruptedException 異常。
- Yield(): 提示系統允許其他等待線程運行。
- Name 和 CurrentThread.Name: 獲取或設置線程的名稱。
- SetData 和 GetData: 在線程范圍內設置和獲取線程本地存儲數據。
- Start(ParameterizedThreadStart) 和 Start(ParameterizedThreadStart, Object): 啟動線程并傳遞參數給線程方法。
- TrySetApartmentState(ApartmentState): 嘗試設置線程的單元狀態,返回是否成功。
- StartNew(Action) 和 StartNew(Action, CancellationToken): 使用 Task 類來啟動線程。
這些方法提供了各種線程管理和操作的能力。然而,需要注意,一些方法已經過時,不推薦使用,而且一些方法可能會涉及多線程編程的復雜性,需要謹慎使用。在編寫多線程應用程序時,確保仔細閱讀文檔并根據需求選擇適當的方法。
(1)創建線程
通常,你需要傳遞一個方法作為線程的入口點,然后調用 Start 方法來啟動線程。
using System;
using System.Threading;class Program
{static void Main(){Thread thread = new Thread(WorkerMethod);thread.Start(); // 啟動線程}static void WorkerMethod(){Console.WriteLine("Thread is running.");}
}
(2) 線程同步
在多線程環境中,線程同步是一種確保多個線程協調工作的機制。Thread 類提供了 Join 方法,允許一個線程等待另一個線程完成。這在需要等待某個線程的結果時特別有用。
using System;
using System.Threading;class Program
{static void Main(){Thread currentThread = Thread.CurrentThread;Console.WriteLine($"Thread ID: {currentThread.ManagedThreadId}");Thread thread = new Thread(WorkerMethod);thread.Start();// 主線程等待子線程完成thread.Join();Console.WriteLine("Thread has finished.");}static void WorkerMethod(){Thread currentThread = Thread.CurrentThread;Console.WriteLine($"Thread ID: {currentThread.ManagedThreadId}");Console.WriteLine("Thread is running.");Thread.Sleep(2000); // 模擬耗時操作}
}
(3)線程異步
using System;
using System.Threading;class Program
{static void Main(){Thread currentThread = Thread.CurrentThread;Console.WriteLine($"Thread ID: {currentThread.ManagedThreadId}");Thread thread = new Thread(WorkerMethod);thread.Start();// 主線程等待子線程完成//thread.Join();Console.WriteLine("Thread has finished.");//這里子線程雖然還沒有處理完,但是直接返回了,沒有繼續等待子線程,但是子線程還在繼續處理工作,沒有出現阻塞現象return "ok";}static void WorkerMethod(){Thread currentThread = Thread.CurrentThread;Console.WriteLine($"Thread ID: {currentThread.ManagedThreadId}");Console.WriteLine("Thread is running.");Thread.Sleep(10000); // 模擬耗時操作//這里在主線程結束后,繼續在處理10s后打印Thread is WordEnd;Console.WriteLine("Thread is WordEnd.");}
}
可以思考下,主線程返回成功了,但是子線程執行失敗了,這可怎么辦?
2.ThreadPool(線程池)
是一個用于管理和重用線程的機制,可以使用 ThreadPool 類來執行異步任務。
3.Task(任務)
表示一個異步操作,可以使用 Task 類或 Task.Run 方法來創建和管理任務。
(1)Task與Thead的關系
4.Task Parallel Library (TPL)(任務并行庫)
是 C# 中用于并行編程的高級庫,用于處理異步和并行操作,包括數據并行和任務并行。
5.Async/Await(異步/等待)
是 C# 5.0 引入的異步編程模型,用于創建和管理異步方法和操作。
6.Monitor(監視器)
是用于實現線程同步的一種機制,用于保護共享資源,避免競態條件。可以使用 Monitor 類或 lock 關鍵字來實現。
首先lock和Minitor有什么區別呢?
其實lock在IL代碼中會被翻譯成Monitor。也就是Monitor.Enter(obj)和Monitor.Exit(obj).
lock(obj)
{}//等價為:
try{ Monitor.Enter(obj)
}catch()
{}
finally
{Monitor.Exit(obj)
}
7.Semaphore(信號量)
用于控制并發訪問資源的數量,可以使用 Semaphore 類來創建和管理信號量。
8.SemaphoreSlim
是 Semaphore 的改進版本,提供更好的性能和可伸縮性。
9.AutoResetEvent(自動復位事件)
用于線程同步,允許一個線程等待另一個線程發出信號。
10.ManualResetEvent(手動復位事件)
用于線程同步,允許一個線程等待多個線程發出信號。
11.CancellationToken(取消標記)
用于在異步操作中請求取消操作,可以在異步方法中傳遞給取消標記。
12.volatile(易失性修飾符)
用于標記字段,指示編譯器不應該對標記字段進行優化,以確保多線程環境下的正確性。
13.Mutex(互斥鎖)
是一種用于實現線程同步的機制,用于保護共享資源,防止多個線程同時訪問。
14.ReaderWriterLock(讀寫鎖)
允許多個線程同時讀取共享資源,但只允許一個線程寫入資源。適用于讀操作頻繁、寫操作較少的場景。
15.ReaderWriterLockSlim(輕量級讀寫鎖)
是 ReaderWriterLock 的改進版本,提供更好的性能和可伸縮性。
16.SpinLock
是一種自旋鎖,用于短時間內的臨界區保護。它使用忙等待來嘗試獲取鎖,適用于臨界區很小的情況。
17.SpinWait
用于在自旋等待期間執行自旋操作,可以根據不同的條件進行自旋。
18.Barrier(屏障)
允許多個線程在一個點上等待,直到所有線程都達到該點。適用于需要所有線程協調同步的場景。
四、多線程的異常捕獲問題
相關文章