2021年4月18日 星期日

.NET System.Threading - 說明範例 2

System.Threading 裡面提供了很多用來控制執行緒的工具

層次比較高,用起來效率高很多

來一個一個介紹起

走起~~


1. CountdownEvent 跟 Interlocked (範例就放一起了)

CountdownEvent 用 AddCount 跟 Signal 來 增加跟減少計數,當計數歸零就可以通過
所以在用的時候,預設會放個1開始,避免連算第一次都沒有

Interlocked 則是用來處理一些非執行緒安全的計算
例如:   i += 1
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 是相同的

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 當作資源本身模擬下用餐的範例

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 (很特殊的情況)

(待補充)

沒有留言:

張貼留言