Version 14.07
デッドロック!
「今回は、同期処理において最も注意しなければならない【デッドロック】
について説明します」
『でっどろっく?』
「英語で deadlock 、どうしようもない完全に固まった状態のことを表しま
す」
『なんか難しそう』
「ううん、全然難しくないよ。たとえば……」
・お店A
・お店B
「があるとします」
『いきなりお店?』
「例だから……このお店には、それぞれ次のようなルールがあります」
・お店A:お店Bが安くしたらうちも安くする。
・お店B:お店Aが安くしたらうちも安くする。
『なにこれ、いつまでたっても安くなんないじゃん』
「それがデッドロック」
『???』
「これをミューテックスに置き換えてみます」
・プロセスA
・プロセスB
「がある場合に、次のようなプログラムが書かれていたとします」
・プロセスA:プロセスBのミューテックスBが解放されたら
ミューテックスAを解放する。
・プロセスB:プロセスAのミューテックスAが解放されたら
ミューテックスBを解放する。
『???』
「図にするとこう」
プロセスA プロセスB
ミューテックスAの作成及び所有。
ミューテックスAを取得。
ミューテックスBの作成及び所有。
ミューテックスBを取得。
ミューテックスAを所有中←←←←←←←ミューテックスAを監視開始。
ミューテックスAを所有中 (監視中)
ミューテックスBを監視開始→→→→→→ミューテックスBを所有中
(監視中) (監視中)
(監視中) (監視中)
...
『げ、両方とも監視中になっちゃった!』
「つまり、AとB両方がミューテックスを持っていて、両方ともシグナルオ
フの時にお互いに監視を始めちゃうと、ずーっと見張りっ放しになっちゃう
んです」
『それまずいじゃん!』
「だからまずいんだって。こういう状態が【デッドロック】』
『まさにどうしようもないね……』
「これを実際にプログラムで試してみます。まず、前回作った ProcessA と
ProcessB のサンプルを用意してください」
『はい』
「それを、こう修正してください」
【ProcessA】
namespace B1
{
HANDLE g_hMutexA = NULL;
HANDLE g_hMutexB = NULL;
const char *const MUTEX_NAME_A = "ProcessesMutexA";
const char *const MUTEX_NAME_B = "ProcessesMutexB";
void CreateA()
{
g_hMutexA = CreateMutex( NULL, TRUE, MUTEX_NAME_A );
TRACE( "%x\n", g_hMutexA );
}
void OpenB()
{
g_hMutexB = OpenMutex( MUTEX_ALL_ACCESS, FALSE, MUTEX_NAME_B );
TRACE( "%x\n", g_hMutexB );
}
void WaitB()
{
TRACE( "待機開始。\n" );
DWORD dwResult = WaitForSingleObject( g_hMutexB, 120000 );
if( dwResult == WAIT_TIMEOUT )
{
TRACE( "タイムアウト。\n" );
}
else
{
TRACE( "待機終了。\n" );
}
}
void ReleaseB()
{
ReleaseMutex( g_hMutexB );
}
void CloseA()
{
CloseHandle( g_hMutexA );
}
}
【ProcessB】
namespace B1
{
HANDLE g_hMutexA = NULL;
HANDLE g_hMutexB = NULL;
const char *const MUTEX_NAME_A = "ProcessesMutexA";
const char *const MUTEX_NAME_B = "ProcessesMutexB";
void CreateB()
{
g_hMutexB = CreateMutex( NULL, TRUE, MUTEX_NAME_B );
TRACE( "%x\n", g_hMutexB );
}
void OpenA()
{
g_hMutexA = OpenMutex( MUTEX_ALL_ACCESS, FALSE, MUTEX_NAME_A );
TRACE( "%x\n", g_hMutexA );
}
void WaitA()
{
TRACE( "待機開始。\n" );
DWORD dwResult = WaitForSingleObject( g_hMutexA, 120000 );
if( dwResult == WAIT_TIMEOUT )
{
TRACE( "タイムアウト。\n" );
}
else
{
TRACE( "待機終了。\n" );
}
}
void ReleaseA()
{
ReleaseMutex( g_hMutexA );
}
void CloseB()
{
CloseHandle( g_hMutexB );
}
}
『長!』
「前回と同じように、各関数をボタンのハンドラから呼ぶようにしてくださ
い」
『なんか違いが微妙すぎていまいちわかんないんだけど』
「簡単に言えば
| 作成 | 待機
ProcessA | ProcessesMutexA | ProcessesMutexB
ProcessB | ProcessesMutexB | ProcessesMutexA
「という感じに、相手が作ったミューテックスを待機の相手にする、ってい
う形になります」
『そか、そうすればさっきのデッドロックになる!』
「というわけで試してみましょう。まず ProcessA::CreateA() を呼んで
ProcessesMutexA を作成」
『ほい』
「次に ProcessB::CreateB() を呼んで ProcessesMutexB を作成」
『ほい』
「次に ProcessB::OpenA() を呼んで ProcessesMutexA のハンドルを取得、
さらに ProcessB::WaitA() を呼んで待機開始」
『でも ProcessA に所有権があるから待ちっぱなし、ね』
「で、この状態の時に ProcessA::OpenB() を呼んで ProcessesMutexB の
ハンドルを取得、そして ProcessA::WaitB() を呼ぶと」
『 ProcessB に所有権があるままだから……お、固まった』
「というわけでデッドロックの完成。 ProcessA ProcessB 両方とも固まっ
たままになります」
『あちゃー……』
「ちなみにこのプログラムはタイムアウト時間を2分に設定してあるから」
『 WaitForSingleObject() の第2引数ね。お、2分待ったら復活した』
「というわけで、デッドロックは気を付けないと簡単に発生します」
『回避方法ってどうすればいいの?』
「それについてはあとでちゃんと説明するけど、とりあえず一番簡単な方法
は〈同期オブジェクトはひとつしか作らない〉ことかな」
『そっか、そうすればお互いにお見合いすることないもんね』
「ほとんどの場合ひとつで十分だし、複数必要でもセマフォを使えばなんと
かなることも多いから」
『セマフォ?』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『せまふぉ、ってなんか言いにくい』
「慣れが必要な単語のひとつかも」
『みゅーてっくすもそうだし』
「でも使えるようになるととたんに気に入るから」
『確かに……セマフォ、セマフォ、セマフォ、かっこいいかも?』
「というわけで次回」
< Version 14.08 セマフォを使ってみる! >
『につづく!』
「ちなみにセマフォは【信号機】って意味」
『うわかっこわる!』