Version 14.21
スレッドでデッドロック
「今回は、以前紹介したデッドロックについて」
『 Version 14.07 ( No.274 ) のだよね。あのときと何が違うの?』
「この前はミューテックスで試したけど、今回はクリティカルセクションで
デッドロックしてみます」
『あー、そういうことね』
// 共有する変数。
int g_iData = 0;
// クリティカルセクション用同期オブジェクト。
CRITICAL_SECTION g_stCriticalSection1;
CRITICAL_SECTION g_stCriticalSection2;
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread1( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
TRACE( "IncrementThread1() 1 と 2 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
EnterCriticalSection( &g_stCriticalSection2 );
TRACE( "IncrementThread1() 2 の後。\n" );
// クリティカルセクションのロックを解除します。
LeaveCriticalSection( &g_stCriticalSection2 );
LeaveCriticalSection( &g_stCriticalSection1 );
}
TRACE( "スレッド終了。\n" );
}
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread2( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
EnterCriticalSection( &g_stCriticalSection2 );
TRACE( "IncrementThread2() 2 と 1 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
TRACE( "IncrementThread2() 1 の後。\n" );
// クリティカルセクションのロックを解除します。
LeaveCriticalSection( &g_stCriticalSection1 );
LeaveCriticalSection( &g_stCriticalSection2 );
}
TRACE( "スレッド終了。\n" );
}
// IncrementThread1() を別スレッドとして呼び出す関数。
// この関数をボタンのイベントハンドラとかで呼んでください。
void CallThread()
{
// クリティカルセクションを初期化します。
InitializeCriticalSection( &g_stCriticalSection1 );
InitializeCriticalSection( &g_stCriticalSection2 );
HANDLE hThreadAry[2];
hThreadAry[0]
= (HANDLE)_beginthread( IncrementThread1, 0, NULL );
hThreadAry[1]
= (HANDLE)_beginthread( IncrementThread2, 0, NULL );
WaitForMultipleObjects( 2, hThreadAry, TRUE, 100 * 1000 );
TRACE( "処理完了。\n" );
TRACE( "%d\n", g_iData );
// クリティカルセクションを削除します。
DeleteCriticalSection( &g_stCriticalSection1 );
DeleteCriticalSection( &g_stCriticalSection2 );
}
『クリティカルセクションがふたつ……そういえば、ミューテックスの時も
そうだったね』
「基本的にデッドロックが起きるのは同期オブジェクトが複数ある場合。
デッドロックする仕組みは……」
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread1( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
TRACE( "IncrementThread1() 1 と 2 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
EnterCriticalSection( &g_stCriticalSection2 );
「と」
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread2( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
EnterCriticalSection( &g_stCriticalSection2 );
TRACE( "IncrementThread2() 2 と 1 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
「で、使用しているクリティカルセクションが逆になってるでしょ。これが
ポイント」
『 IncrementThread1() は 1 → 2 の順、 IncrementThread2() は 2 → 1
の順なんだね』
「この順でロックを掛けると……」
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread1( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
TRACE( "IncrementThread1() 1 と 2 の間。\n" );
// 0.1 秒待ちます。
// スレッド1がここで 0.1 秒待っている間に……
Sleep( 100 );
EnterCriticalSection( &g_stCriticalSection2 );
「このように、スレッド1が Sleep() で 1 と 2 の間で待っている間に」
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread2( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
EnterCriticalSection( &g_stCriticalSection2 );
TRACE( "IncrementThread2() 2 と 1 の間。\n" );
// 0.1 秒待ちます。
// スレッド2もここで待っていると……
Sleep( 100 );
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
「スレッド2が入ってきちゃうと、スレッド1が……」
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread1( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
TRACE( "IncrementThread1() 1 と 2 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
// スレッド1がここでロック解除待ち。
EnterCriticalSection( &g_stCriticalSection2 );
「と、スレッド2が掛けたロックが解けるのを待つことになります」
『で、スレッド2も……』
// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread2( void *p_p )
{
TRACE( "スレッド開始。\n" );
for( int iF1 = 0; iF1 < 10; ++iF1 )
{
EnterCriticalSection( &g_stCriticalSection2 );
TRACE( "IncrementThread2() 2 と 1 の間。\n" );
// 0.1 秒待ちます。
Sleep( 100 );
// スレッド2がここでロック解除待ち。
// クリティカルセクションでロックを掛けます。
EnterCriticalSection( &g_stCriticalSection1 );
『スレッド1が掛けたロックが解けるのを待っちゃう、でもスレッド1も
待ってるわけだから』
「どうしようもなくなっちゃいます」
『げ』
「しかも WaitForSingleObject() と違ってタイムアウトしないから」
『……永久?』
「というわけで、このプログラムは強制終了してください」
/*
Preview Next Story!
*/
『ホントに簡単にデッドロックしちゃうね』
「ふたつ同期オブジェクトがあると簡単になっちゃうから」
『……あ!』
「はい火美ちゃん」
『いいアイディア思いついたんだけど!』
「というわけで次回」
< Version 14.22 複数の同期オブジェクト >
『につづく!』
「次回はそのアイディアの問題点についても」
『ってまだ言ってねー!!』