示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> #include <thread> #include <mutex> std::mutex mutex1; std::mutex mutex2; void threadFunction1() { std::lock_guard<std::mutex> lock1(mutex1); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作 std::lock_guard<std::mutex> lock2(mutex2); std::cout << "Thread 1 finished work" << std::endl; } void threadFunction2() { std::lock_guard<std::mutex> lock2(mutex2); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作 std::lock_guard<std::mutex> lock1(mutex1); std::cout << "Thread 2 finished work" << std::endl; } int main() { std::thread t1(threadFunction1); std::thread t2(threadFunction2); t1.join(); t2.join(); return 0; } |
WinDbg分析
启动调试
- 编译并运行程序
- 使用
VS
编译生成test.exe
,并双击运行
- 使用
- 附加到进程
- 启动
WinDbg
,选择File > Attach to Process
,找到并选择运行的死锁程序
- 启动
- 暂停程序执行
- 在
WinDbg
中,点击工具栏上的暂停按钮Debug -> Break
- 在
分析:检查线程状态
- 输入以下命令查看当前所有线程
1 |
~ |
- 使用以下命令查看当前线程的调用堆栈
1 |
~* kb |
- 示例
1 2 3 4 5 6 7 8 |
0:004> ~ 0 Id: 66d8.69f4 Suspend: 1 Teb: 000000be`0cd2d000 Unfrozen 1 Id: 66d8.920 Suspend: 1 Teb: 000000be`0cd2f000 Unfrozen 2 Id: 66d8.6eb8 Suspend: 1 Teb: 000000be`0cd31000 Unfrozen 3 Id: 66d8.1600 Suspend: 1 Teb: 000000be`0cd33000 Unfrozen . 4 Id: 66d8.55ec Suspend: 1 Teb: 000000be`0cd35000 Unfrozen 5 Id: 66d8.3124 Suspend: 1 Teb: 000000be`0cd37000 Unfrozen # 6 Id: 66d8.73e8 Suspend: 1 Teb: 000000be`0cd39000 Unfrozen |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
0:004> ~* kb 0 Id: 66d8.69f4 Suspend: 1 Teb: 000000be`0cd2d000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`47da920e : cccccccc`cccccccc cccccccc`cccccccc cccccccc`cccccccc 00007ff7`ab77145b : ntdll!NtWaitForSingleObject+0x14 01 00007ffb`ee9327e4 : 00000000`00000000 00007ffb`ee9327ba 00000020`00000000 00000000`000000d4 : KERNELBASE!WaitForSingleObjectEx+0x8e 02 00007ff7`ab774581 : 000000be`0cbbfa60 00000000`00000000 c3a7d800`00000047 00000000`00000000 : MSVCP140D!_Thrd_join+0x24 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\cthread.cpp @ 53] 03 00007ff7`ab774d49 : 000000be`0cbbfac8 00007ff7`ab771145 00000264`341a6100 00007ffb`3100f21e : leetcode_temp!std::thread::join+0x81 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 133] 04 00007ff7`ab775b89 : 0000320f`00000001 00007ffb`310825c8 00000000`00000000 00007ff7`ab77771d : leetcode_temp!main+0x69 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 33] 05 00007ff7`ab775a32 : 00007ff7`ab77e000 00007ff7`ab77e220 00000000`00000000 00000000`00000000 : leetcode_temp!invoke_main+0x39 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 79] 06 00007ff7`ab7758ee : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : leetcode_temp!__scrt_common_main_seh+0x132 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 07 00007ff7`ab775c1e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : leetcode_temp!__scrt_common_main+0xe [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331] 08 00007ffc`48307374 : 000000be`0cd2c000 00000000`00000000 00000000`00000000 00000000`00000000 : leetcode_temp!mainCRTStartup+0xe [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17] 09 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 0a 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 1 Id: 66d8.920 Suspend: 1 Teb: 000000be`0cd2f000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a23d407 : 00000000`00000000 00000000`00000000 00000264`341a4ce0 00000264`341a5d10 : ntdll!NtWaitForWorkViaWorkerFactory+0x14 01 00007ffc`48307374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7 02 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 2 Id: 66d8.6eb8 Suspend: 1 Teb: 000000be`0cd31000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a23d407 : 00000000`00010017 00000000`00000001 00000264`00000001 00000264`341a7e70 : ntdll!NtWaitForWorkViaWorkerFactory+0x14 01 00007ffc`48307374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7 02 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 3 Id: 66d8.1600 Suspend: 1 Teb: 000000be`0cd33000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a23d407 : 00000000`00000000 00000000`00000000 00000264`341a4ce0 00000264`341a60b0 : ntdll!NtWaitForWorkViaWorkerFactory+0x14 01 00007ffc`48307374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7 02 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 4 Id: 66d8.55ec Suspend: 1 Teb: 000000be`0cd35000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a219205 : 00007ff7`ab78906f 000010bc`66c5ac50 000000be`0d1ff618 00000000`00000000 : ntdll!NtWaitForAlertByThreadId+0x14 01 00007ffb`ee932df3 : 00007ff7`00000000 cccccccc`cccccccc 00000000`00000000 000000be`0d1ff8b8 : ntdll!RtlAcquireSRWLockExclusive+0x165 02 00007ffb`ee932b65 : 00007ff7`ab783050 00000000`00000000 00007ff7`ab78906f 000000be`0d1ff8b8 : MSVCP140D!mtx_do_lock+0xb3 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 93] 03 00007ff7`ab7746f4 : 00007ff7`ab783050 00000000`00000000 00000000`00000330 000000be`0d1ff658 : MSVCP140D!_Mtx_lock+0x15 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 165] 04 00007ff7`ab773aa5 : 00007ff7`ab783050 00007ffb`31065cc3 00007ffb`3106548a 00007ffb`310661ce : leetcode_temp!std::_Mutex_base::lock+0x34 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 52] 05 00007ff7`ab774a65 : 000000be`0d1ff858 00007ff7`ab783050 00000000`00000000 00007ffc`4a2202c9 : leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 06 00007ff7`ab7737b9 : 00007ff7`ab789038 00007ffb`31065c20 00000000`00000015 00000000`00000000 : leetcode_temp!threadFunc1+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 18] 07 00007ff7`ab773037 : 00000264`341ac290 00000264`341ac290 00000000`00000000 ffffffff`fffffffe : leetcode_temp!std::invoke<void (__cdecl*)(void)>+0x29 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\type_traits @ 1704] 08 00007ffb`31082f95 : 00000264`341ac290 00000000`00000000 00000000`00000000 00000000`00000000 : leetcode_temp!std::thread::_Invoke<std::tuple<void (__cdecl*)(void)>,0>+0x87 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 60] 09 00007ffc`48307374 : 00000264`341aa5f0 00000000`00000000 00000000`00000000 00000000`00000000 : ucrtbased!thread_start<unsigned int (__cdecl*)(void *),1>+0xa5 [minkernel\crts\ucrt\src\appcrt\startup\thread.cpp @ 97] 0a 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 0b 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 5 Id: 66d8.3124 Suspend: 1 Teb: 000000be`0cd37000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a219205 : 00007ff7`ab78906f 000010bc`66c59abc 000000be`0d2ff8e8 00000000`00000000 : ntdll!NtWaitForAlertByThreadId+0x14 01 00007ffb`ee932df3 : 00007ff7`00000000 cccccccc`cccccccc 00000000`00000000 000000be`0d2ffb88 : ntdll!RtlAcquireSRWLockExclusive+0x165 02 00007ffb`ee932b65 : 00007ff7`ab783000 00000000`00000000 00007ff7`ab78906f 000000be`0d2ffb88 : MSVCP140D!mtx_do_lock+0xb3 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 93] 03 00007ff7`ab7746f4 : 00007ff7`ab783000 00000000`00000000 00000264`00000330 000000be`0d2ff928 : MSVCP140D!_Mtx_lock+0x15 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 165] 04 00007ff7`ab773aa5 : 00007ff7`ab783000 00007ffb`31065cc3 00007ffb`3106548a 00007ffb`310661ce : leetcode_temp!std::_Mutex_base::lock+0x34 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 52] 05 00007ff7`ab774b85 : 000000be`0d2ffb28 00007ff7`ab783000 00000000`00000000 00007ffc`4a2202c9 : leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 06 00007ff7`ab7737b9 : 00007ff7`ab789038 00007ffb`31065c20 00000000`00000015 00000000`00000000 : leetcode_temp!threadFunc2+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 25] 07 00007ff7`ab773037 : 00000264`341abb10 00000264`341abb10 00000000`00000000 ffffffff`fffffffe : leetcode_temp!std::invoke<void (__cdecl*)(void)>+0x29 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\type_traits @ 1704] 08 00007ffb`31082f95 : 00000264`341abb10 00000000`00000000 00000000`00000000 00000000`00000000 : leetcode_temp!std::thread::_Invoke<std::tuple<void (__cdecl*)(void)>,0>+0x87 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 60] 09 00007ffc`48307374 : 00000264`341aac10 00000000`00000000 00000000`00000000 00000000`00000000 : ucrtbased!thread_start<unsigned int (__cdecl*)(void *),1>+0xa5 [minkernel\crts\ucrt\src\appcrt\startup\thread.cpp @ 97] 0a 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 0b 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 # 6 Id: 66d8.73e8 Suspend: 1 Teb: 000000be`0cd39000 Unfrozen # RetAddr : Args to Child : Call Site 00 00007ffc`4a2bcafe : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgBreakPoint 01 00007ffc`48307374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgUiRemoteBreakin+0x4e 02 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 |
- 分析线程
4
- 调用堆栈显示,线程
4
卡在了等待互斥锁的过程中,从堆栈顶端可以看出线程在NtWaitForAlertByThreadId
上挂起,这通常表示线程正在等待某个锁的释放 MSVCP140D!mtx_do_lock+0xb3
和MSVCP140D!_Mtx_lock+0x15
表示线程正在试图锁定一个互斥锁leetcode_temp!std::_Mutex_base::lock+0x34
和leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>+0x45
显示代码正在使用std::mutex
和std::lock_guard
进行锁操作,这些代码出现在死锁的关键位置- 堆栈显示锁的操作发生在文件
leetcode_temp.cpp
的第18
行,也就是threadFunc1
函数中
- 调用堆栈显示,线程
- 分析线程
5
- 表现和线程
4
一样 - 线程
5
也卡在了等待互斥锁的过程中(线程正在等待某个锁的释放) - 和线程
4
一样,线程5
正在试图锁定一个互斥锁 - 和线程
4
一样,线程5
也在使用std::mutex
和std::lock_guard
进行锁操作,这些代码出现在死锁的关键位置 - 不同的是,堆栈显示锁的操作发生在文件
leetcode_temp.cpp
的第25
行,也就是threadFunc2
函数中
- 表现和线程
- 结论
- 观察到在线程
4
和线程5
中看到了锁相关内容 threadFunc1
是线程4
的入口函数,threadFunc2
是线程5
的入口函数- 在这两个函数中,都是先使用
std::lock_guard<std::mutex>
锁了一个std::mutex
,然后在等待另一个锁的释放 - 可以得出死锁发生在了这两个函数中
- 观察到在线程
进一步查看线程信息
- 在上一步确定它们发生了死锁的情况下,进一步验证
- 切到目标线程
1 |
~4s |
- 查看堆栈
1 |
kp |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
0:004> kp # Child-SP RetAddr Call Site 00 000000be`0d1ff508 00007ffc`4a219205 ntdll!NtWaitForAlertByThreadId+0x14 01 000000be`0d1ff510 00007ffb`ee932df3 ntdll!RtlAcquireSRWLockExclusive+0x165 02 000000be`0d1ff580 00007ffb`ee932b65 MSVCP140D!mtx_do_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783050, struct _timespec64 * target = 0x00000000`00000000)+0xb3 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 93] 03 000000be`0d1ff5e0 00007ff7`ab7746f4 MSVCP140D!_Mtx_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783050)+0x15 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 165] 04 000000be`0d1ff610 00007ff7`ab773aa5 leetcode_temp!std::_Mutex_base::lock(void)+0x34 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 52] 05 000000be`0d1ff710 00007ff7`ab774a65 leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>(class std::mutex * _Mtx = 0x00007ff7`ab783050)+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 06 000000be`0d1ff810 00007ff7`ab7737b9 leetcode_temp!threadFunc1(void)+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 18] 07 000000be`0d1ff990 00007ff7`ab773037 leetcode_temp!std::invoke<void (<function> ** _Obj = 0x00000264`341ac290)+0x29 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\type_traits @ 1704] 08 000000be`0d1ffa90 00007ffb`31082f95 leetcode_temp!std::thread::_Invoke<std::tuple<void (void * _RawVals = 0x00000264`341ac290)+0x87 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 60] 09 000000be`0d1ffbf0 00007ffc`48307374 ucrtbased!thread_start<unsigned int (void * parameter = 0x00000264`341aa5f0)+0xa5 [minkernel\crts\ucrt\src\appcrt\startup\thread.cpp @ 97] 0a 000000be`0d1ffc50 00007ffc`4a23cc91 KERNEL32!BaseThreadInitThunk+0x14 0b 000000be`0d1ffc80 00000000`00000000 ntdll!RtlUserThreadStart+0x21 |
- 发现第6条堆栈信息,表示是 刚进入线程
- 查看局部变量以及变量地址
- 也可以直接查看全局变量(知道两个互斥量是全局的情况下,见下文)
1 2 3 |
0:004> dv lock2 = class std::lock_guard<std::mutex> lock1 = class std::lock_guard<std::mutex> |
1 2 3 4 5 6 |
0:004> ?? &lock1 class std::lock_guard<std::mutex> * 0x000000be`0d1ff838 +0x000 _MyMutex : 0x00007ff7`ab783000 std::mutex 0:004> ?? &lock2 class std::lock_guard<std::mutex> * 0x000000be`0d1ff858 +0x000 _MyMutex : 0x00007ff7`ab783050 std::mutex |
- 知道了两个锁的地址后,看看在线程
4
里面等待的是哪一个锁- 可以看到是
0x00007ff7ab783050
这个,也就是线程4
等待的是lock2
- 可以看到是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
0:004> kp # Child-SP RetAddr Call Site 00 000000be`0d1ff508 00007ffc`4a219205 ntdll!NtWaitForAlertByThreadId+0x14 01 000000be`0d1ff510 00007ffb`ee932df3 ntdll!RtlAcquireSRWLockExclusive+0x165 02 000000be`0d1ff580 00007ffb`ee932b65 MSVCP140D!mtx_do_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783050, struct _timespec64 * target = 0x00000000`00000000)+0xb3 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 93] 03 000000be`0d1ff5e0 00007ff7`ab7746f4 MSVCP140D!_Mtx_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783050)+0x15 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 165] 04 000000be`0d1ff610 00007ff7`ab773aa5 leetcode_temp!std::_Mutex_base::lock(void)+0x34 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 52] 05 000000be`0d1ff710 00007ff7`ab774a65 leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>(class std::mutex * _Mtx = 0x00007ff7`ab783050)+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 06 000000be`0d1ff810 00007ff7`ab7737b9 leetcode_temp!threadFunc1(void)+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 18] 07 000000be`0d1ff990 00007ff7`ab773037 leetcode_temp!std::invoke<void (<function> ** _Obj = 0x00000264`341ac290)+0x29 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\type_traits @ 1704] 08 000000be`0d1ffa90 00007ffb`31082f95 leetcode_temp!std::thread::_Invoke<std::tuple<void (void * _RawVals = 0x00000264`341ac290)+0x87 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 60] 09 000000be`0d1ffbf0 00007ffc`48307374 ucrtbased!thread_start<unsigned int (void * parameter = 0x00000264`341aa5f0)+0xa5 [minkernel\crts\ucrt\src\appcrt\startup\thread.cpp @ 97] 0a 000000be`0d1ffc50 00007ffc`4a23cc91 KERNEL32!BaseThreadInitThunk+0x14 0b 000000be`0d1ffc80 00000000`00000000 ntdll!RtlUserThreadStart+0x21 0:004> .frame 5 05 000000be`0d1ff710 00007ff7`ab774a65 leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 0:004> dv this = 0x000000be`0d1ff858 _Mtx = 0x00007ff7`ab783050 |
- 同理,可以看到线程
5
里面等待的是0x00007ff7ab783000
,也就是lock1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
0:004> ~5s ntdll!NtWaitForAlertByThreadId+0x14: 00007ffc`4a290f94 c3 ret 0:005> kp # Child-SP RetAddr Call Site 00 000000be`0d2ff7d8 00007ffc`4a219205 ntdll!NtWaitForAlertByThreadId+0x14 01 000000be`0d2ff7e0 00007ffb`ee932df3 ntdll!RtlAcquireSRWLockExclusive+0x165 02 000000be`0d2ff850 00007ffb`ee932b65 MSVCP140D!mtx_do_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783000, struct _timespec64 * target = 0x00000000`00000000)+0xb3 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 93] 03 000000be`0d2ff8b0 00007ff7`ab7746f4 MSVCP140D!_Mtx_lock(struct _Mtx_internal_imp_t * mtx = 0x00007ff7`ab783000)+0x15 [D:\a\_work\1\s\src\vctools\crt\github\stl\src\mutex.cpp @ 165] 04 000000be`0d2ff8e0 00007ff7`ab773aa5 leetcode_temp!std::_Mutex_base::lock(void)+0x34 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 52] 05 000000be`0d2ff9e0 00007ff7`ab774b85 leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>(class std::mutex * _Mtx = 0x00007ff7`ab783000)+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 06 000000be`0d2ffae0 00007ff7`ab7737b9 leetcode_temp!threadFunc2(void)+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 25] 07 000000be`0d2ffc60 00007ff7`ab773037 leetcode_temp!std::invoke<void (<function> ** _Obj = 0x00000264`341abb10)+0x29 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\type_traits @ 1704] 08 000000be`0d2ffd60 00007ffb`31082f95 leetcode_temp!std::thread::_Invoke<std::tuple<void (void * _RawVals = 0x00000264`341abb10)+0x87 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\thread @ 60] 09 000000be`0d2ffec0 00007ffc`48307374 ucrtbased!thread_start<unsigned int (void * parameter = 0x00000264`341aac10)+0xa5 [minkernel\crts\ucrt\src\appcrt\startup\thread.cpp @ 97] 0a 000000be`0d2fff20 00007ffc`4a23cc91 KERNEL32!BaseThreadInitThunk+0x14 0b 000000be`0d2fff50 00000000`00000000 ntdll!RtlUserThreadStart+0x21 0:005> .frame 5 05 000000be`0d2ff9e0 00007ff7`ab774b85 leetcode_temp!std::lock_guard<std::mutex>::lock_guard<std::mutex>+0x45 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\mutex @ 454] 0:005> dv this = 0x000000be`0d2ffb28 _Mtx = 0x00007ff7`ab783000 |
查看全局变量
- 查看所有符号
1 |
x leetcode_temp!* |
- 查看
mutex
相关符号
1 |
x leetcode_temp!*mutex* |
查看锁被哪个线程占有
- 还是像之前一样,先切到线程开始的地方,具体查看两个锁
1 2 3 4 5 |
0:005> .frame 6 06 000000be`0d2ffae0 00007ff7`ab7737b9 leetcode_temp!threadFunc2+0x85 [Q:\qym_code\leetcode_temp\leetcode_temp\leetcode_temp.cpp @ 25] 0:005> dv lock2 = class std::lock_guard<std::mutex> lock1 = class std::lock_guard<std::mutex> |
- 点击
lock1
对象的内部数据,可以看到:- 它被线程
21996(10进制)
占有
- 它被线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
0:005> dx -r1 (*((leetcode_temp!std::lock_guard<std::mutex> *)0xbe0d2ffb28)) (*((leetcode_temp!std::lock_guard<std::mutex> *)0xbe0d2ffb28)) [Type: std::lock_guard<std::mutex>] [+0x000] _MyMutex : 0x7ff7ab783000 [Type: std::mutex &] 0:005> dx -r1 ((leetcode_temp!std::mutex *)0x7ff7ab783000) ((leetcode_temp!std::mutex *)0x7ff7ab783000) : 0x7ff7ab783000 [Type: std::mutex *] [+0x000] _Mtx_storage [Type: _Mtx_internal_imp_t] 0:005> dx -r1 (*((leetcode_temp!_Mtx_internal_imp_t *)0x7ff7ab783000)) (*((leetcode_temp!_Mtx_internal_imp_t *)0x7ff7ab783000)) [Type: _Mtx_internal_imp_t] _Critical_section_size : 0x40 [Type: unsigned __int64] [+0x000] _Type : 2 [Type: int] [+0x008] _Critical_section [Type: _Stl_critical_section] [+0x008] _Cs_storage [Type: std::_Align_type<double,64>] [+0x048] _Thread_id : 21996 [Type: long] [+0x04c] _Count : 1 [Type: int] |
- 可以看到
21996
线程占用了lock1
- 对
16
进制进行转换,是55ec
线程占有了lock1
- 对
1 2 |
0:005> ? 0n21996 Evaluate expression: 21996 = 00000000`000055ec |
- 查看所有线程
- 可以看到,
windbg
里面的4
号线程就是占有了lock1
的55ec
线程 66d8
表示的是进程
- 可以看到,
1 |
~ |
1 2 3 4 5 6 7 8 |
0:005> ~ 0 Id: 66d8.69f4 Suspend: 1 Teb: 000000be`0cd2d000 Unfrozen 1 Id: 66d8.920 Suspend: 1 Teb: 000000be`0cd2f000 Unfrozen 2 Id: 66d8.6eb8 Suspend: 1 Teb: 000000be`0cd31000 Unfrozen 3 Id: 66d8.1600 Suspend: 1 Teb: 000000be`0cd33000 Unfrozen 4 Id: 66d8.55ec Suspend: 1 Teb: 000000be`0cd35000 Unfrozen . 5 Id: 66d8.3124 Suspend: 1 Teb: 000000be`0cd37000 Unfrozen # 6 Id: 66d8.73e8 Suspend: 1 Teb: 000000be`0cd39000 Unfrozen |
- 用同样的方法
- 可以看到
3124
线程(也就是windbg
里面的5
号线程)占有了lock2
- 可以看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
0:004> dx -r1 (*((leetcode_temp!std::lock_guard<std::mutex> *)0xbe0d2ffb08)) (*((leetcode_temp!std::lock_guard<std::mutex> *)0xbe0d2ffb08)) [Type: std::lock_guard<std::mutex>] [+0x000] _MyMutex : 0x7ff7ab783050 [Type: std::mutex &] 0:004> dx -r1 ((leetcode_temp!std::mutex *)0x7ff7ab783050) ((leetcode_temp!std::mutex *)0x7ff7ab783050) : 0x7ff7ab783050 [Type: std::mutex *] [+0x000] _Mtx_storage [Type: _Mtx_internal_imp_t] 0:004> dx -r1 (*((leetcode_temp!_Mtx_internal_imp_t *)0x7ff7ab783050)) (*((leetcode_temp!_Mtx_internal_imp_t *)0x7ff7ab783050)) [Type: _Mtx_internal_imp_t] _Critical_section_size : 0x40 [Type: unsigned __int64] [+0x000] _Type : 2 [Type: int] [+0x008] _Critical_section [Type: _Stl_critical_section] [+0x008] _Cs_storage [Type: std::_Align_type<double,64>] [+0x048] _Thread_id : 12580 [Type: long] [+0x04c] _Count : 1 [Type: int] |
1 2 |
0:004> ? 0n12580 Evaluate expression: 12580 = 00000000`00003124 |
综上所述
windbg
的4
号线程占有了lock1
,在等待lock2
windbg
的5
号线程占有了lock2
, 在等待lock1
- 所以它们发生了死锁
其他分析:检查锁
- 使用以下命令查找每个线程的持有和等待锁的信息
- 这个命令是
WinDbg
中ext.dll
扩展的命令,用于显示当前进程中所有锁的状态
- 这个命令是
1 |
!locks |
- 示例:
1 2 3 |
0:006> !locks Scanned 5 critical sections |
- 结论
- 并没有看到相关信息
!locks
主要是针对临界区(Critical Sections
)进行分析- 如果你的程序使用了其他同步机制(如
std::mutex
或其他自定义锁),!locks
可能无法正确识别这些锁
使用其他调试工具继续分析
- 对于标准的 C++ 互斥锁(如
std::mutex
),可以使用其他调试扩展- 如:
!analyze -v
:尝试自动分析可能的死锁和线程问题
- 如:
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
0:004> !analyze -v ******************************************************************************* * * * Exception Analysis * * * ******************************************************************************* KEY_VALUES_STRING: 1 Key : Analysis.CPU.mSec Value: 1812 Key : Analysis.DebugAnalysisProvider.CPP Value: Create: 8007007e on DESKTOP-NRIG174 Key : Analysis.DebugData Value: CreateObject Key : Analysis.DebugModel Value: CreateObject Key : Analysis.Elapsed.mSec Value: 1801 Key : Analysis.Init.CPU.mSec Value: 53811 Key : Analysis.Init.Elapsed.mSec Value: 1881012 Key : Analysis.Memory.CommitPeak.Mb Value: 135 Key : Analysis.System Value: CreateObject Key : Timeline.OS.Boot.DeltaSec Value: 20061 Key : Timeline.Process.Start.DeltaSec Value: 1659 Key : WER.OS.Branch Value: vb_release Key : WER.OS.Timestamp Value: 2019-12-06T14:06:00Z Key : WER.OS.Version Value: 10.0.19041.1 ADDITIONAL_XML: 1 OS_BUILD_LAYERS: 1 NTGLOBALFLAG: 0 PROCESS_BAM_CURRENT_THROTTLED: 0 PROCESS_BAM_PREVIOUS_THROTTLED: 0 APPLICATION_VERIFIER_FLAGS: 0 EXCEPTION_RECORD: (.exr -1) ExceptionAddress: 00007ffc4a291090 (ntdll!DbgBreakPoint) ExceptionCode: 80000003 (Break instruction exception) ExceptionFlags: 00000000 NumberParameters: 1 Parameter[0]: 0000000000000000 FAULTING_THREAD: 000073e8 PROCESS_NAME: leetcode_temp.exe ERROR_CODE: (NTSTATUS) 0x80000003 - { } EXCEPTION_CODE_STR: 80000003 EXCEPTION_PARAMETER1: 0000000000000000 STACK_TEXT: 000000be`0d3ffb18 00007ffc`4a2bcafe : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgBreakPoint 000000be`0d3ffb20 00007ffc`48307374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgUiRemoteBreakin+0x4e 000000be`0d3ffb50 00007ffc`4a23cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 000000be`0d3ffb80 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 STACK_COMMAND: ~6s ; .cxr ; kb SYMBOL_NAME: ntdll!DbgBreakPoint+0 MODULE_NAME: ntdll IMAGE_NAME: ntdll.dll FAILURE_BUCKET_ID: BREAKPOINT_80000003_ntdll.dll!DbgBreakPoint OS_VERSION: 10.0.19041.1 BUILDLAB_STR: vb_release OSPLATFORM_TYPE: x64 OSNAME: Windows 10 IMAGE_VERSION: 10.0.19041.4522 FAILURE_ID_HASH: {1d20f983-583a-5d58-a854-5c812a87c587} Followup: MachineOwner --------- |
- 示例信息解析:
00007ffc4a291090 (ntdll!DbgBreakPoint)
表示程序在调试器的中断点处停止
这通常是因为调试器手动插入了一个中断来暂停程序执行80000003 (Break instruction exception)
表示一个断点异常,这通常由调试器触发,并不是因为程序的错误逻辑,而是调试器通过插入断点来中止程序执行Analysis.CPU.mSec: 1812
表示CPU
花费了1812
毫秒进行当前分析Analysis.Elapsed.mSec: 1801
表示整个分析过程花费了1801
毫秒Timeline.OS.Boot.DeltaSec: 20061
自上次系统启动以来的秒数,表明系统已经运行了大约5.5
小时Timeline.Process.Start.DeltaSec: 1659
自进程启动以来的秒数,表明程序运行了大约27
分钟后进入调试状态Process Name: leetcode_temp.exe
这是当前正在调试的程序名称Module Name: ntdll
引发断点异常的模块是ntdll.dll
,Windows
核心模块之一,常见于处理低级别的系统调用Image Name: ntdll.dll
这是导致异常的模块的镜像名称Symbol Name: ntdll!DbgBreakPoint+0
显示异常是由ntdll
模块中的DbgBreakPoint
函数引发的Failure Bucket ID: BREAKPOINT_80000003_ntdll.dll!DbgBreakPoint
这个Bucket ID
用于标识调试报告中的断点异常,是一种分类标识符,帮助Microsoft
分析错误报告Stack Text
: 显示了堆栈的调用链:
1 2 3 4 5 6 7 8 9 |
// 当前线程因为 DbgBreakPoint 中断,这是一个调试中断,通常是手动插入的或用于调试目的 ntdll!DbgBreakPoint // 调试器的远程中断例程 ntdll!DbgUiRemoteBreakin+0x4e // 下面这些函数用于线程的启动和初始化,是标准的系统级线程启动过程 KERNEL32!BaseThreadInitThunk+0x14 ntdll!RtlUserThreadStart+0x21 |
其他总结
关于程序的死锁
- 一个程序发生死锁后,如果没有其他线程在执行任务,那么该程序的
CPU
使用率通常会接近0%
- 死锁发生时,涉及死锁的线程会进入阻塞状态(例如等待互斥锁、信号量等资源的释放)
- 被阻塞的线程不会进行任何活动,因为它们处于等待状态,不会占用
CPU
时间 - 虽然
CPU
使用率为0%
,但程序可能仍然占用其他系统资源(如内存、文件句柄等),这些资源在死锁解除前不会被释放
- 一个线程进入(死锁、等待消息、等待某内核对象)阻塞状态意味着什么
- 意味着该线程不会向
CPU
提出任何执行请求
- 意味着该线程不会向
关于程序的死循环
CPU
有12
个物理核心和24
个逻辑核心(线程)- 通常这表示每个物理核心有
2
个逻辑线程(即超线程技术) - 每个逻辑核心可以独立执行任务
- 通常这表示每个物理核心有
- 当程序进入死循环时,它的执行线程将不断占用
CPU
资源进行计算,不会释放控制权- 这个线程会独占一个逻辑核心的所有计算资源
- 死循环和时间片
- 即使是处于死循环的线程,它的执行依然受到操作系统调度器的控制,具体表现为线程会被分配一个时间片
- 当时间片耗尽时,调度器会中断该线程的执行,将其挂起并选择下一个就绪的线程执行
- 死循环线程的特性是它在每次被调度到
CPU
执行时,会完全利用分配给它的时间片,不会主动释放控制权(因为它没有阻塞、等待等操作) - 当它的时间片用尽时,虽然会暂时被挂起,但由于它仍然在就绪队列中,调度器很快会再次选择它执行
- 由于每次被调度到时,它都会立刻占用
CPU
资源进行计算,因此它会反复不断地占满分配给它的计算资源
关于GUI 程序消息循环
GUI
程序通常包含一个消息循环,它的运行机制可以被理解为一个持续运行的循环(类似死循环)- 但这种消息循环与真正的“死循环”是有所不同的
- 消息循环虽然看起来是一个“死循环”,但它通常不会占用大量的
CPU
资源- 因为在没有消息需要处理时,它会进入等待状态(例如
GetMessage
函数会阻塞) - 关于等待状态,见上一条关于程序的死锁
- 因为在没有消息需要处理时,它会进入等待状态(例如
线程的工作机制
- 概述
- 线程是程序的基本执行单元,是调度和分配
CPU
时间的基本实体 - 一个程序可以包含一个或多个线程,每个线程独立执行
- 线程是程序的基本执行单元,是调度和分配
- 线程状态
- 新建状态(
New
):线程被创建但未开始运行 - 就绪状态(
Ready
):线程准备好执行,等待操作系统分配CPU
- 运行状态(
Running
):线程被CPU
调度器分配时间片,正在执行 - 阻塞状态(
Blocked/Waiting
):线程因等待资源或事件而暂停执行,不占用CPU
- 终止状态(
Terminated
):线程完成了执行或因异常终止
- 新建状态(
- 线程如何向
CPU
提出执行请求- 就绪队列
当线程创建并进入就绪状态时,它会被放入操作系统的就绪队列中
就绪队列中的线程表示它们已经准备好执行,等待CPU
分配时间片 - 调度请求
线程向CPU
提出执行请求的本质是被操作系统的调度器从就绪队列中选择并分配CPU
时间片
线程本身不会主动请求CPU
,而是由调度器根据调度算法(如轮询调度、优先级调度等)来决定哪个线程可以执行 - 线程的状态转换
就绪到运行:当调度器选择某个线程执行时,线程从就绪状态切换到运行状态
运行到阻塞:线程因等待I/O
操作、锁定资源或等待事件进入阻塞状态
阻塞到就绪:当等待条件满足(如I/O
完成、资源释放),线程返回就绪队列等待重新调度
- 就绪队列
CPU
如何响应线程的执行请求CPU
调度器
CPU
调度器是操作系统内核的一部分,负责在就绪队列中选择线程,将CPU
时间片分配给它们- 时间片
每个线程在被调度到CPU
上时会分配一个时间片
时间片结束后,线程会被挂起,切换到另一个线程
通过时间片轮转,CPU
可以在多个线程之间快速切换,形成线程“并发”执行的效果 - 中断和抢占
CPU
调度依赖于时钟中断,时钟中断是调度器抢占正在运行的线程并选择下一个线程执行的时机
抢占机制确保高优先级的线程可以优先执行,即使它们在低优先级线程的时间片内出现
- 调度器的工作机制如下:
- 上下文切换(
Context Switch
) - 当调度器决定让某个线程运行时,会执行上下文切换
- 上下文切换包括保存当前运行线程的状态(寄存器、程序计数器等),然后加载即将运行的线程的状态
- 上下文切换(
- 理解:
- 程序
A
的线程可以被CPU
的任何一个逻辑核心执行,具体由操作系统的调度器决定 - 而任何一个逻辑核心在执行某个线程时,都可以通过线程本地存储(
Thread Local Storage
,TLS
)和线程的上下文信息来加载并恢复该线程的最后一次执行状态,从而继续执行之前未完成的任务 - 总体来讲,每个逻辑核心虽然任何时刻只能执行一个线程,但由于时间片分配非常短(通常在几毫秒级别),逻辑核心会很快得频繁切换执行不同的线程,使得我们感知不到这个交替过程
- 程序
CPU 执行线程的详细过程
- 线程上下文保存与恢复
- 当一个线程的时间片结束或被调度器挂起时,操作系统会保存该线程的上下文信息
包括:
寄存器状态(如通用寄存器、程序计数器、栈指针等)
线程本地存储(TLS
):用于存储线程特定的数据
线程栈:保存函数调用、局部变量、返回地址等 - 当线程被重新调度时,这些保存的上下文会被加载到
CPU
的寄存器中,使得线程可以从上次暂停的位置继续执行
- 当一个线程的时间片结束或被调度器挂起时,操作系统会保存该线程的上下文信息
- 逻辑核心执行线程的机制
- 当调度器决定让一个线程在某个逻辑核心上运行时,
CPU
会通过上下文切换机制恢复线程的执行状态 CPU
将寄存器恢复到该线程最后一次保存的状态,包括程序计数器(PC
)的位置,这个计数器指向下一条待执行的指令地址
- 当调度器决定让一个线程在某个逻辑核心上运行时,
- 指令的获取和执行
- 取指令,如下:
- 程序计数器中的值通过 地址总线 被发送到系统的内存控制器
(通常是RAM
中的某个物理地址,也就是程序的代码段) - 内存控制器接收地址请求后,通过 数据总线 返回该地址处存储的指令(通常是机器码形式的指令)
- 指令的解码与执行
- 解码(
Decode
)
CPU
解析指令,确定要执行的操作类型(例如算术运算、逻辑运算、内存访问等) - 执行(
Execute
)
CPU
执行指令,完成指令中指定的操作(如寄存器之间的数据传输、内存读写、算术逻辑运算等)
- 解码(
- 指令结果的处理
- 执行结果可能会更新
CPU
的寄存器、改变内存中的数据,或者修改程序计数器的值以指向下一条指令
- 执行结果可能会更新
- 循环重复执行
- 这一过程会不断重复:取指令、解码、执行、更新,直到时间片结束或线程被中断
- 总结:
- 当调度器决定让一个线程在某个逻辑核心上运行时,
CPU
会通过上下文切换机制恢复线程的执行状态 - 然后,程序计数器中的值通过 地址总线 被发送到系统的内存控制器
- 内存控制器接收地址请求后,通过 数据总线 返回该地址处存储的指令
- 在指令通过数据总线传输到
CPU
之前,内存控制器会发出一个“数据就绪”信号,告知CPU
数据已经准备好 - 在
CPU
接收到“数据就绪”信号之前,可能会进入短暂的等待状态,确保不会在数据未传输完成时开始操作 - 一旦数据总线上的指令准备好,
CPU
会锁存(Latch
)这些数据到内部指令寄存器(Instruction Register
,IR
)中 CPU
的时钟信号与控制总线的“数据就绪”信号同步,以确保数据被正确接收CPU
会检查控制信号的状态,以确定数据传输是否成功,例如通过错误检测信号(如奇偶校验、ECC
检查)来确保数据完整性CPU
将指令从指令寄存器中读取,并通过解码单元解析该指令- 根据解码结果,执行相应的操作(如算术、逻辑、内存访问等)
- 当调度器决定让一个线程在某个逻辑核心上运行时,
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Windows物理内存虚拟内存03/28
- ♥ Windows 核心编程 _ 进程三06/19
- ♥ 打包_7z生成自解压打包exe07/11
- ♥ WindowsHOOK相关03/17
- ♥ Soui四05/23
- ♥ X86_64汇编学习记述三08/08