上一篇文章主要帶領大家認識了執行緒,也瞭解到了執行緒的基本用法和狀態,接下來就讓我們一起學習下什麼是執行緒同步。
執行緒中異常的處理
線上程中始終使用try/catch程式碼塊是非常重要的,因為不可能線上程程式碼之外來捕獲到異常。
可以閱讀下面的程式碼,這塊是做的驗證,證明線上程之外捕獲異常是錯誤的選擇,應該線上程中時時刻刻都使用異常處理機制。
static void Main(string[] args)
{
Thread twoThread = new Thread(TwoMethod);
twoThread.Start();
twoThread.Join();
try
{
Thread oneThread = new Thread(OneMethod);
oneThread.Start();
}
catch (Exception ex)
{
Console.WriteLine("外部捕獲執行緒one的異常:"+ex.Message);
}
Console.ReadKey();
}
static void OneMethod()
{
Console.WriteLine("Start OneMethod");
Thread.Sleep(TimeSpan.FromSeconds(2));
throw new Exception("異常01");
}
static void TwoMethod()
{
try
{
Console.WriteLine("Start TwoMethod");
Thread.Sleep(TimeSpan.FromSeconds(1));
throw new Exception("異常02");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
下面圖片是輸出報錯的結果,可以看到OneThread的異常沒有被外部的try/catch捕獲到,導致直接線上程內部提示錯誤,導致程式崩潰。
看到這個情況,那麼我們在以後使用執行緒的時候,就需要特別的注意,一定要線上程中進行異常的處理和捕獲,千萬別遺留任何未處理的異常,因為如果執行緒中有未被處理的異常會導致整個程式都會受到影響,可能導致整個軟體崩潰。
執行緒同步
在上一篇推文中,我們瞭解到了Lock加鎖的機制,它是可以保證將某個變數或者某個模組鎖住,當出現多個執行緒同時訪問時鎖就會起作用,只允許一個執行緒訪問,其餘的等待,其訪問完後其餘的才可以進行訪問。但是這種機制有一定的侷限性,在多核CPU裝置中,讓其餘執行緒等待是極大浪費資源的,而且這種解決辦法會導致死鎖的現象。
上面說的也就是所謂的競爭條件問題的解決方法,導致這個問題的原因是多執行緒的執行並沒有正確同步。當一個執行緒執行遞增和遞減操作時,其他執行緒需要依次等待。這種常見問題通常被稱為執行緒同步。這種問題出現在當執行緒中有共享資源或者物件時,才會進行執行緒同步,如果無共享物件,則無需進行執行緒同步。
當線上程中有共享資源時,可使用下面的兩種方式進行處理。
一、原子操作
來實現對共享資源的訪問。其實就是一個操作只佔用一個量子的時間,一次就可以完成。也就是說只有當前操作完成後,其他執行緒才能執行其他操作。這樣就避免了使用鎖,排除了死鎖的情況。
原子操作就是使用C#系統自帶的Interlocked類來對執行緒不安全的物件進行處理,藉助Interlocked類,無需鎖定任何物件即可獲取到正確的結果,Interlocked類提供Increment,Decrement和Add等基本數學操作的原子方法,從而可以幫助我們無需使用鎖?,避免出現各種死鎖問題。
/// <summary>
/// 執行緒不安全
/// </summary>
class Counter
{
private int _count;
public int Count { get { return _count; } }
public void Add()
{
_count++;
}
public void Delete()
{
_count--;
}
}
/// <summary>
/// 不加鎖 ,但執行緒是安全的。
/// 可避免線上程中出現死鎖
/// </summary>
class CounterNoLock
{
private int _count;
public int Count { get { return _count; } }
public void Add()
{
Interlocked.Increment(ref _count); //遞增
}
public void Delete()
{
Interlocked.Decrement(ref _count); //遞減
}
}
二、將等待的執行緒置於阻塞狀態
這個處理方法是在第一個原子操作無效切程式的邏輯更加複雜的情況下才使用的,用於協調執行緒。
當執行緒處理阻塞狀態時,只會佔用儘可能少的CPU時間,這就意味著將引入至少一次所謂的上下文切換。
上下文切換:指作業系統的執行緒排程器,該排程器會保持等待的執行緒的狀態,並切換到另一個執行緒,依次恢復等待的執行緒狀態。雖然會消耗極大的資源,但是如果執行緒被掛起很長時間這麼做是值得的。這種也就核心模式,因為只有作業系統的核心才能阻止執行緒使用CPU時間。
使用者模式: 如果執行緒只是等候一小會,那最好只是簡單的等待,而不用將執行緒切換到阻塞狀態。還有一種為混合模式,也就是先嚐試使用使用者模式,如果執行緒等候時間過長,則會切換到阻塞狀態以節省CPU資源。
下面的DEMO主要介紹SemaphoreSlim類,該類用於限制了同時訪問同一個資源的執行緒數量。
static void Main(string[] args)
{
for (int i = 1; i <=6; i++)
{
string threadName = "Thread " + i;
int secondsWait = 2;
var thread = new Thread(( )=>DataConnect(threadName,secondsWait));
thread.Start();
}
Console.ReadKey();
}
static SemaphoreSlim _semaphore = new SemaphoreSlim(4); //預設4個執行緒可同時訪問
static void DataConnect(string name,int seconds)
{
Console.WriteLine("wait 執行緒的名字:",name);
_semaphore.Wait();
Console.WriteLine("Connect 執行緒的名字:" + name);
Thread.Sleep(TimeSpan.FromSeconds(seconds));
_semaphore.Release();
}
上面的程式碼利用SemaphoreSlim類,設定其建構函式為4,也就是其指定允許的併發執行緒數量。上面使用訊號系統限制了訪問資料連線的併發數為4個,當有4個執行緒進行訪問時,其他兩個執行緒需要等待,知道之前執行緒中某一個完成工作並呼叫Relece方法來發出訊號。
小寄語
人生短暫,我不想去追求自己看不見的,我只想抓住我能看的見的。
原創不易,給個關注。
我是阿輝,感謝您的閱讀,如果對你有幫助,麻煩點贊、轉發 謝謝。