我個人覺得使用c#開發各種應用程式是很幸福的,
因為MS線上Docs都會有相關Best Practices,
這篇來看看MTA(Multi-Thread Apartments)的管理。
自從TPL問世,寫多執行緒就像一塊蛋糕一樣,
但管理Threads的邏輯還是得靠開發人員自己,不然可能會發生deadlock或結果不如預期
@Race conditions
多條執行緒,存取共同全域變數,就會發生非預期結果,
好比,我預期每條執行緒都有相同的workload,來看看下面code
Using Thread
static async Task<int> UseThread()
{
Thread t1 = new Thread(() => PrintName("rico"));
t1.Start();
Thread t2 = new Thread(() => PrintName("sherry"));
t2.Start();
return 1;
}
private static int counter;
static void PrintName(string name)
{
for (counter = 0; counter < 5; counter++)
{
Console.Write($" {name} {counter} " + "\n");
}
}
可以看到輸出結果不符預期(output is inconsistent),而這就是Race conditions,
如何避免這情況呢?我原本想說synchronization method即可,修改如下
static async Task<int> UseThread()
{
Thread t1 = new Thread(() => PrintName("rico"));
t1.Start();
t1.Join();
Thread t2 = new Thread(() => PrintName("sherry"));
t2.Start();
t1.Join();
//main thread will always execute after t1 and t2 completes its execution
return 1;
}
可以看到結果就如我們所預期,每條thread的workload都是一致的(都輸出5次)
前面有提到,TPL開發所帶來的效率和高可讀性,來看看TPL的版本
static async Task<int> UseTPL()
{
var t1 = Task.Factory.StartNew(() => PrintName("rico"));
var t2 = t1.ContinueWith(antacedent => PrintName("sherry"));
//ContinueWith can help avoid race conditions
Task.WaitAll(new Task[] { t1, t2 });
return 1;
}
上面解法看上去很OK,但沒想到同步還是有可能發生Race conditions,我還是無法理解,
我遇到是在多台Server(每台server有32條logical processor)情況,查看MS的Docs說明如下
Race conditions can also occur when you synchronize the activities of multiple threads. Whenever you write a line of code, you must consider what might happen if a thread were preempted before executing the line (or before any of the individual machine instructions that make up the line), and another thread overtook it.
看來比較保險的方法,就是透過lock或Monitor,
來確保當下只能有一條thread可以存取該block(可能影響效能),
thread-safe改寫如下
tatic async Task<int> UseThread()
{
Thread t1 = new Thread(() => PrintName("rico"));
t1.Start();
//t1.Join();
Thread t2 = new Thread(() => PrintName("sherry"));
t2.Start();
//t1.Join();
return 1;
}
Using Lock
private static object locker = new object();
static void PrintName(string name)
{
lock (locker)
{
for (counter = 0; counter < 5; counter++)
{
Console.Write($" {name} {counter} " + "\n");
}
}
}
Using Monitor
static void PrintName(string name)
{
Monitor.Enter(locker);
try
{
for (counter = 0; counter < 5; counter++)
{
Console.Write($" {name} {counter} " + "\n");
}
}
finally
{
Monitor.Exit(locker);
}
}
目前觀察超過一個禮拜,還沒有發生Race conditions情況。
參考
Managed Threading Best Practices