デッドロック

分散環境やマルチスレッド環境等、実行コンテキストが複数存在する場合、 共有リソースを取り合って、双方が身動き取れなくなってしまう場合があります。 これをデッドロックといいます。

クリティカルセクションによるデッドロックの解析例

テストプログラムの作成

#include <windows.h>
#include <stdio.h>
#include <process.h>


CRITICAL_SECTION g_cs1;
CRITICAL_SECTION g_cs2;


unsigned int __stdcall ThreadFunc (void* arg) {

     EnterCriticalSection( &g_cs2 );

     EnterCriticalSection ( &g_cs1 );

     printf( "Hello, ThreadFunc!\n" );

     EnterCriticalSection ( &g_cs1 );
     
     LeaveCriticalSection ( &g_cs2 );

     return 0;
}


int main(int argc, char* argv[]) {

     unsigned uThreadId;
     HANDLE hWorkerThread;

     InitializeCriticalSection ( &g_cs1 );
     InitializeCriticalSection ( &g_cs2 );
     

     // EnterCriticalSection
     EnterCriticalSection ( &g_cs1 );

     // Thread
     hWorkerThread = (HANDLE) _beginthreadex(
               NULL, 
               NULL, 
               ThreadFunc, 
               (PVOID) NULL, // param, 
               0, 
               &uThreadId);

     if( hWorkerThread ) {
          CloseHandle(hWorkerThread);
     }

     //
     Sleep ( 1 * 1000 );

     EnterCriticalSection ( &g_cs2 );

     printf ( "Hello, main!\n" );
     
     LeaveCriticalSection ( &g_cs2 );
          
     LeaveCriticalSection ( &g_cs1 );


     DeleteCriticalSection ( &g_cs1 );
     DeleteCriticalSection ( &g_cs2 );
     return 0;
}

makefile は次の通りです。

TARGETNAME=test1
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32\
	/DUNICODE\
	/D_UNICODE\
		
LINK32_FLAGS=\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG\
	/RELEASE
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $< 

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

.cxx{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

上記のコードをビルドして、test1.exe を作成します。

test1.exe を実行するとプログラムは何もせず、終了もしません。

解析例

この状態でデバッガをアタッチして、何が起きているかみてみましょう。

0:002> !locks

CritSec test1!g_cs1+0 at 0041af00
WaiterWoken        No
LockCount          1
RecursionCount     1
OwningThread       1538
EntryCount         0
ContentionCount    1
*** Locked

CritSec test1!g_cs2+0 at 0041af18
WaiterWoken        No
LockCount          1
RecursionCount     1
OwningThread       1ba4
EntryCount         0
ContentionCount    1
*** Locked

Scanned 37 critical sections
0:002> ~
   0  Id: 1fa0.1538 Suspend: 1 Teb: 7ffde000 Unfrozen
   1  Id: 1fa0.1ba4 Suspend: 1 Teb: 7ffdd000 Unfrozen
.  2  Id: 1fa0.1964 Suspend: 1 Teb: 7ffdc000 Unfrozen
0:002> ~0 kbn
 # ChildEBP RetAddr  Args to Child
00 0012fe9c 770b9254 770a33b4 0000000c 00000000 ntdll!KiFastSystemCallRet
01 0012fea0 770a33b4 0000000c 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
02 0012ff04 770a323c 00000000 00000000 7ffdf000 ntdll!RtlpWaitOnCriticalSection+0x155
03 0012ff2c 004010b9 0041af18 00000010 00001ba4 ntdll!RtlEnterCriticalSection+0x152
04 0012ff40 0040157e 00000001 00212320 00212340 test1!main+0x69
05 0012ff88 76d64911 7ffdf000 0012ffd4 7709e4b6 test1!__tmainCRTStartup+0xfb
06 0012ff94 7709e4b6 7ffdf000 7e1b530f 00000000 kernel32!BaseThreadInitThunk+0xe
07 0012ffd4 7709e489 004015d5 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23
08 0012ffec 00000000 004015d5 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:002> ~1 kbn
 # ChildEBP RetAddr  Args to Child
00 0031fea8 770b9254 770a33b4 00000010 00000000 ntdll!KiFastSystemCallRet
01 0031feac 770a33b4 00000010 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
02 0031ff10 770a323c 00000000 00000000 002111f0 ntdll!RtlpWaitOnCriticalSection+0x155
03 0031ff38 00401019 0041af00 0031ff7c 004012ba ntdll!RtlEnterCriticalSection+0x152
04 0031ff44 004012ba 00000000 1d7c2567 00000000 test1!ThreadFunc+0x19
05 0031ff7c 00401362 00000000 0031ff94 76d64911 test1!_callthreadstartex+0x1b
06 0031ff88 76d64911 002111f0 0031ffd4 7709e4b6 test1!_threadstartex+0x82
07 0031ff94 7709e4b6 002111f0 7e38530f 00000000 kernel32!BaseThreadInitThunk+0xe
08 0031ffd4 7709e489 004012e0 002111f0 00000000 ntdll!__RtlUserThreadStart+0x23
09 0031ffec 00000000 004012e0 002111f0 00000000 ntdll!_RtlUserThreadStart+0x1b
0:002>

上記のログから、クリティカルセクション 0041af00 のオーナースレッドは スレッド ID 1538 であることがわかります。 そして、スレッド ID 1538 (=スレッド#0) はそのスタックから、クリティカルセクション 0041af18 を取得するところで停止していることがわかります。

また、クリティカルセクション 0041af18 のオーナースレッドは同様にスレッド #1 であることがわかりますが、 そのスレッドは、クリティカルセクション 0041af00 に入るところで停止しています。

ダンプを採取することによって、デッドロックの状態がこのようにはっきりわかります。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説