[C#]Manager Threading

我個人覺得使用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

Synchronizing Data for Multithreading

How To Avoid Busy Waiting