ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

デッドロック回避

プログラミングC# p526

ロックすべき共有リソースが二つある場合、そのアクセス順を同じにしておかないとデッドロックが発生します。
例えばこんな感じ。

using System;
using System.Threading;

class ThreadTest
{
        private static Object lockObj1 = new Object();
        private static Object lockObj2 = new Object();

        static void Main(string[] args)
        {
                ThreadStart ts1 = new ThreadStart(delegate(){
                        Monitor.Enter(lockObj1);
                        Monitor.Enter(lockObj2);
                });
                ThreadStart ts2 = new ThreadStart(delegate(){
                        Monitor.Enter(lockObj2);
                        Monitor.Enter(lockObj1);
                });

                Thread t1 = new Thread(ts1);
                Thread t2 = new Thread(ts2);

                t1.Start();
                t2.Start();

                t1.Join();
                t2.Join();
        }
}

このプログラムを実行すると、ダンマリ状態になります。(Ctrl+Cで終了してください)


これを防ぐには、Monitor#TryEnterを使います。

using System;
using System.Threading;

class ThreadTest
{
        private static Object lockObj1 = new Object();
        private static Object lockObj2 = new Object();

        static void Main(string[] args)
        {
                ThreadStart ts1 = new ThreadStart(delegate(){
                        Boolean blnLock1 = Monitor.TryEnter(lockObj1, 100);
                        if(blnLock1)
                        {
                                Console.WriteLine("Thread1 Lock1 Success.");

                                Boolean blnLock2 = Monitor.TryEnter(lockObj2);
                                Console.WriteLine("Thread1 Lock2 {0}", blnLock2)
;
                        }
                        else
                        {
                                Console.WriteLine("Thread1 Lock1 Fail.");
                        }
                });
                ThreadStart ts2 = new ThreadStart(delegate(){
                        Boolean blnLock2 = Monitor.TryEnter(lockObj2, 100);
                        if(blnLock2)
                        {
                                Console.WriteLine("Thread2 Lock2 Success.");

                                Boolean blnLock1 = Monitor.TryEnter(lockObj1);
                                Console.WriteLine("Thread2 Lock1 {0}", blnLock1)
;
                        }
                        else
                        {
                                Console.WriteLine("Thread2 Lock2 Fail.");
                        }
                });

                Thread t1 = new Thread(ts1);
                Thread t2 = new Thread(ts2);

                t1.Start();
                t2.Start();

                t1.Join();
                t2.Join();
        }
}

結果はこう。

Thread1 Lock1 Success.
Thread1 Lock2 True
Thread2 Lock2 Fail.

というわけで、Thread2はLock2の取得に失敗しています。
常識的には、Thread2もLock1 → Lock2の順番に処理するように変更するのでしょうが、やむをえない場合は、Lock2が取得できるまでwaitする(TryEnterの戻りがTrueになるまでSleepでループする)のも手でしょう。