層次比較高,用起來效率高很多
來一個一個介紹起
走起~~
1. CountdownEvent 跟 Interlocked (範例就放一起了)
CountdownEvent 用 AddCount 跟 Signal 來 增加跟減少計數,當計數歸零就可以通過
所以在用的時候,預設會放個1開始,避免連算第一次都沒有
Interlocked 則是用來處理一些非執行緒安全的計算
例如: i += 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var count = 10000; | |
var value = 0; | |
// 由於 value += 1 並不是 thread-safe,所以當 count 夠大的時候 value 最後的加總,很難剛好等於 4*count | |
// 因為使用了 ThreadPool 碰不到直接的 Thread 來 Join,改用 CountdownEvent Wait | |
using(var cdEvent = new CountdownEvent(1)) | |
{ | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) value += 1; cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) value += 1; cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) value += 1; cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) value += 1; cdEvent.Signal();}); | |
cdEvent.Signal(); | |
cdEvent.Wait(); | |
} | |
Console.WriteLine($"{value} != {4 * count}"); | |
/* | |
19989 != 40000 | |
*/ | |
// value += 1 改用 Interlocked.Add 取代 (thread safe!!) | |
value = 0; | |
using(var cdEvent = new CountdownEvent(1)) | |
{ | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) Interlocked.Add(ref value, 1); cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) Interlocked.Add(ref value, 1); cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) Interlocked.Add(ref value, 1); cdEvent.Signal();}); | |
cdEvent.AddCount(); | |
ThreadPool.QueueUserWorkItem((s) => { for (int i = 0; i < count; i++) Interlocked.Add(ref value, 1); cdEvent.Signal();}); | |
cdEvent.Signal(); | |
cdEvent.Wait(); | |
} | |
Console.WriteLine($"{value} == {4 * count}"); | |
/* | |
40000 == 40000 | |
*/ |
2. Monitor 跟 lock 陳述式
value += 1;
也可以用 lock(obj) { value += 1; }
來鎖定只有一個執行續通過
lock 其實 C# 的語法糖,下面兩個 IL code 是相同的
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var obj = new object(); | |
lock (obj) | |
{ | |
} | |
// ----- | |
var obj2 = new object(); | |
var islocken = false; | |
try | |
{ | |
Monitor.Enter(obj2, ref islocken); | |
} | |
finally | |
{ | |
if(islocken) | |
Monitor.Exit(obj2); | |
} |
3. Mutex
把 Mutex 當作資源本身模擬下用餐的範例
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var knife = new Mutex(); | |
var fork = new Mutex(); | |
var mealSize = 10; | |
var p1 = new Thread(EatType1); | |
p1.Name = "小明"; | |
var p2 = new Thread(EatType1); | |
p2.Name = "大強"; | |
p1.Start(); | |
p2.Start(); | |
p1.Join(); | |
p2.Join(); | |
// 雖然共用餐具有點噁心,但是可以順利用完餐的 | |
p1 = new Thread(EatType1); | |
p1.Name = "小明"; | |
p2 = new Thread(EatType2); | |
p2.Name = "大強"; | |
p1.Start(); | |
p2.Start(); | |
p1.Join(); | |
p2.Join(); | |
// 最後大致會停在互相等待對方手上的餐具,導致無法順利用完餐 | |
// 但是有機會可以用完餐的 | |
/* | |
... | |
小明 - 拿起刀子 | |
小明 - 等待叉子 | |
大強 - 拿起叉子 | |
大強 - 等待刀子 | |
*/ | |
void EatType1() | |
{ | |
for(var i = 0 ; i < mealSize; i++) | |
{ | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 等待刀子"); | |
knife.WaitOne(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 拿起刀子"); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 等待叉子"); | |
fork.WaitOne(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 拿起叉子"); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 用餐進度 {i+1}/{mealSize}"); | |
knife.ReleaseMutex(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 放下刀子"); | |
fork.ReleaseMutex(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 放下叉子"); | |
} | |
} | |
void EatType2() | |
{ | |
for(var i = 0 ; i < mealSize; i++) | |
{ | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 等待叉子"); | |
fork.WaitOne(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 拿起叉子"); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 等待刀子"); | |
knife.WaitOne(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 拿起刀子"); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 用餐進度 {i+1}/{mealSize}"); | |
knife.ReleaseMutex(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 放下刀子"); | |
fork.ReleaseMutex(); | |
Console.WriteLine($"{Thread.CurrentThread.Name} - 放下叉子"); | |
} | |
} |
4. Semaphore
(待補充)5. Barrier
(待補充)6. Timer
(待補充)7. CancellationToken & CancellationTokenSource
(待補充)8. Volatile (很特殊的情況)
(待補充)
沒有留言:
張貼留言