前一篇文章記錄了簡單的多線程編程的幾種方式,但是在實際的項目中,也需要等待多線程執行完成之後再執行的方法,這個就叫做多線程的同步,或者,由於多個線程對同一對象的同時操作造成數據錯亂,需要線程安全。這篇文章主要記錄多線程的同步非同步如何實現線程安全的幾種方式的筆記,如有錯誤,請大神不吝賜教。 因為代碼里 ...
前一篇文章記錄了簡單的多線程編程的幾種方式,但是在實際的項目中,也需要等待多線程執行完成之後再執行的方法,這個就叫做多線程的同步,或者,由於多個線程對同一對象的同時操作造成數據錯亂,需要線程安全。這篇文章主要記錄多線程的同步非同步如何實現線程安全的幾種方式的筆記,如有錯誤,請大神不吝賜教。
因為代碼裡面有很詳細的註釋,所以下麵直接附上代碼,不做過多的解釋,如有疑問可以百度相關主題的文章詳細瞭解。
1、 Mutex
////1.Mutex測試
////Mutex互斥鎖,用於多線程間的線程同步通過WaitOne等待當前鎖定的線程執行完成,例如,線程B執行需要等待線程A執行結束的情況下,可以使用Mutex
////同時Mutex還有一個比較有趣的功能就是可以設置實現客戶端在同一太電腦上只能打開一個進程
//bool createNew = false;
//Mutex mutex = new Mutex(true, "MutexTest", out createNew);
//AutoResetEvent ae = new AutoResetEvent(false);//定義一個信號量,表示執行結束,可以釋放互斥鎖
////參數1表示初始化的時候當前互斥鎖是否已被獲取,false代表未被獲取,
////參數2標識當前互斥鎖的名稱,指定一個名稱,配合參數3即可實現只能開啟一個進程的效果
////參數3表示是否創建了一個新的互斥鎖
//Thread t1 = new Thread(new ThreadStart(() =>
//{
// Console.WriteLine("我是線程1");
// Console.WriteLine("線程1開始執行!");
// Thread.Sleep(1000);//線程休眠1秒鐘,用於模擬需要較長時間執行的功能
// Console.WriteLine("線程1執行結束!");
// ae.Set();
//}));
//Thread t2 = new Thread(() =>
// {
// Console.WriteLine("我是線程2");
// Console.WriteLine("線程2開始執行!");
// mutex.WaitOne();//等待互斥鎖被釋放,模擬實際項目中需要其他線程執行完畢方可執行的功能
// Console.WriteLine("線程2執行結束!");
// });
////因為是多線程執行,所以線程1與線程2的誰先開始執行,以上代碼中未進行控制,
////但線程2一定是線上程1執行完成之後才能結束
//t1.Start();
//t2.Start();
//ae.WaitOne();//等待釋放信息
//mutex.ReleaseMutex();//釋放互斥鎖
////AutoResetEvent的功能類似於一個紅綠燈信號,當達到可以釋放的條件的時候,調用Set方法來通知後續代碼可以執行了,
////此處為何需要一個信號,是因為Mutex定義在主線程中,如果在非同步線程中釋放,會報一個錯,提示在不安全的代碼塊中執行
////互斥鎖,所以此處使用信號來通知主線程可以釋放互斥鎖了
2、AutoResetEvent
/// <summary>
/// 通過AutoRestEvent實現線程同步
/// </summary>
public void TestAutoResetEvent()
{
AutoResetEvent[] autoResetEvents = new AutoResetEvent[3];
autoResetEvents[0] = new AutoResetEvent(false);//定義初始信號為關
autoResetEvents[1] = new AutoResetEvent(false);
autoResetEvents[2] = new AutoResetEvent(false);
//以下代碼實現線程1結束之後線程2才能結束,線程2結束之後線程3才能開始,所有線程都結束之後主線程才能繼續
Thread t1 = new Thread(new ThreadStart(() =>
{
Console.WriteLine("線程1開始!");
Thread.Sleep(1000);
Console.WriteLine("線程1結束!");
autoResetEvents[0].Set();
}));
Thread t2 = new Thread(new ThreadStart(() =>
{
Console.WriteLine("線程2開始!");
Thread.Sleep(1000);
autoResetEvents[0].WaitOne();
Console.WriteLine("線程2結束!");
autoResetEvents[1].Set();
}));
Thread t3 = new Thread(new ThreadStart(() =>
{
autoResetEvents[1].WaitOne();
Console.WriteLine("線程3開始!");
Thread.Sleep(1000);
Console.WriteLine("線程3結束!");
autoResetEvents[2].Set();
}));
t1.Start();
t2.Start();
t3.Start();
Console.WriteLine("主線程開始等待......");
autoResetEvents[2].WaitOne();//等待所有線程結束
//AutoResetEvent從字面即可知道是自動信號,意思為當信號被捕捉之後會自動重置為關閉狀態
//對應的ManualResetEvent為手動信號,使用方法相同但是在被捕捉之後不會被重置為關閉狀態
//需要手動調用Reset方法關閉信號,如果是簡單的同步,使用自動信號即可,如果需要很複雜的流程式控制制
//可以使用自動信號,同時可以配合WaitHandle來實現線程的同步,WaitHandle擁有WaitAny方法等待任意一個信號
//WaitAll方法等待所有信號,使用方法與信號的WaiOne相似,此處不再進行舉例,可以查看相關文章具體瞭解
Console.WriteLine("主線程執行結束!");
}
3、 lock與Monitor
/// <summary>
/// 測試lock和Monitor實現線程安全的多線程
/// </summary>
public void TestLockAndMonitor()
{
//lock與monitor實現相同的功能,多線程的線程安全
//lock實際上就是Monitor.Enter與Monitor.Exit的語法糖
object obj = new object();//創建一個應用類型用於lock
int count = 1;
int sum = 0;
for (int i = 0; i < 20; i++)
{
Thread t = new Thread(new ThreadStart(() =>
{
for (int j = 0; j < 1000; j++)
{
//此處保證線程安全的原理是,當前多個線程同時訪問count的時候,如果不lock
//可能多個線程訪問到的count是相同的值,這樣雖然多個線程都執行了count++但是
//結果卻沒有加上去,造成最終的結果錯誤,當lock之後,lock內部的代碼每次只能
//有一個線程訪問,所以每個線程獲取的count都不可能相同,這樣就能保證最後的結果一定是正確的
//lock (obj)//取消此句代碼測試多線程的不安全性,取消之後可能每次執行的結果都不一樣
//{
// sum += count;
// count++;
//}
//使用下麵的方法與使用lock的功能相同
//Monitor.Enter(obj);
//sum += count;
//count++;
//Monitor.Exit(obj);
}
}));
t.Start();
}
Thread.Sleep(3000);//延時3秒保證非同步線程全部執行完成
Console.WriteLine(sum);
}
4、信號量
/// <summary>
/// 測試信號量實現線程安全
/// </summary>
public void TestSemaphore()
{
//Semaphore 類似於線程池,用於設置同時可以有多少個線程執行
//當線程超過信號量運行的最大值之後,後續的線程就需要等待
Semaphore semaphore = new Semaphore(2, 2);//用於設置最大可以有兩個線程同時執行,初始時有兩個位置空閑
for (int i = 0; i <= 20; i++)
{
Thread t = new Thread(new ThreadStart(() =>
{
semaphore.WaitOne();//等待信號釋放,若未超過信號的最大數值,則不需等待
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Random random = new Random();
Thread.Sleep(random.Next(1,5) * 1000);//隨機休眠1-5秒
semaphore.Release();//釋放當前信號
}));
t.Start();
}
}
5、 自旋鎖
/// <summary>
/// 測試自旋鎖
/// </summary>
public void TestSpinLocked()
{
//自旋鎖與lock實現的功能相同,但是lock鎖住對象開銷比較大
//相反自旋鎖開銷比較小,效率相對也比lock高,當鎖住的次數比較多,同時鎖的時間比較短的時候,可是使用自旋鎖
int count = 1;
int sum = 0;
SpinLock spinLock = new SpinLock();
for (int i = 0; i < 20; i++)
{
Thread t = new Thread(new ThreadStart(() =>
{
bool lockTaken = false;
//使用下麵的方法與使用lock的功能相同
//申請獲取鎖
spinLock.Enter(ref lockTaken);
for (int j = 0; j < 1000; j++)
{
sum += count;
count++;
}
if (lockTaken) //判斷當前線程是否鎖住,如果鎖住則釋放它,防止出現死鎖的情況
{
spinLock.Exit();
}
}));
t.Start();
}
Thread.Sleep(3000);//延時3秒保證非同步線程全部執行完成
Console.WriteLine(sum);
}
6、 原子操作
/// <summary>
/// 測試InterLocked
/// </summary>
public void TestInterLocked()
{
//InterLocked擁有幾個方法來保證線程安全,每個操作都是原子級的,所以效率高,線程安全
//此方法使用InterLocked實現類似於自旋鎖的功能
//關於InterLocked的更多用法請參考MSDN
double current = 0;
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(() =>
{
while (Interlocked.Exchange(ref current, 1) == 1)
{
//此迴圈用於等待當前捕獲current的線程執行結束
}
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Random random = new Random();
Thread.Sleep(random.Next(1, 3) * 1000);//隨機休眠1-3秒
Interlocked.Exchange(ref current, 0);//將current重置為0
}));
t.Start();
}
}
以上的代碼僅僅是筆記用途,沒有深入講解各個方式的優缺點及用途,只是大概的解釋知道的這些方法,有興趣的話,大家可以結合每一個主題的文章詳細瞭解其用法及優缺點,謝謝!