首先给出有死锁问题的代码
g++ -g -lpthread lock.cpp -o lock
lock.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> // 添加此头文件以支持 std::chrono::milliseconds
using std::cout;
using std::mutex;
std::mutex mutexA, mutexB;
using std::lock_guard;
using std::this_thread::sleep_for; // 使用 sleep_for 而不是整个 this_thread 命名空间
/*
有两个线程 Thread 1 和 Thread 2,它们尝试获取 mutexA 和 mutexB,但由于加锁顺序不同,导致死锁。
* */
void thread1()
{
lock_guard<mutex> lockA(mutexA); // 先获取 mutexA
sleep_for(std::chrono::milliseconds(100)); // 模拟线程执行时间
lock_guard<mutex> lockB(mutexB); // 再获取 mutexB
cout << "Thread 1: got mutexB" << std::endl; // 这行代码不会被执行
}
// 线程2
void thread2()
{
lock_guard<mutex> lockB(mutexB); // 先获取 mutexB
sleep_for(std::chrono::milliseconds(100)); // 模拟线程执行时间
lock_guard<mutex> lockA(mutexA); // 再获取 mutexA
cout << "Thread 2: got mutexA" << std::endl; // 这行代码不会被执行
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
return 0;
}
thread1 首先获取 mutexA,然后尝试获取 mutexB。
thread2 首先获取 mutexB,然后尝试获取 mutexA。
如果 thread1 成功获取了 mutexA,并且在等待 mutexB 的过程中,thread2 成功获取了 mutexB 并等待 mutexA,那么 thread1 和 thread2 将永远等待对方释放互斥锁,从而形成死锁。
以下是使用gdb调试该死锁程序的具体步骤:
- 编译带调试信息的程序
g++ -g lock.cpp -o lock -lpthread
- 启动gdb调试
gdb ./lock
- 设置关键断点(在gdb提示符下)
b thread1 # 在thread1入口设置断点
b thread2 # 在thread2入口设置断点
b lock.cpp:18 # 在获取mutexB的位置设置断点
b lock.cpp:30 # 在获取mutexA的位置设置断点
- 运行程序
run
-
当程序出现死锁时(不会正常退出),按Ctrl+C中断程序
-
查看线程状态
info threads
- 切换到线程1查看堆栈
thread 1
bt
- 切换到线程2查看堆栈
thread 2
bt
- 分析死锁状态:
- 检查每个线程持有的锁:
print mutexA # 查看mutexA状态(持有者线程ID)
print mutexB # 查看mutexB状态(持有者线程ID)
- 调试输出分析:
- 当两个线程分别卡在:
thread1卡在lock.cpp:18(等待mutexB)
thread2卡在lock.cpp:30(等待mutexA) - 同时通过print命令确认:
mutexA被thread1持有
mutexB被thread2持有
- 强制退出调试:
quit
调试结果解读:
- 线程1的调用栈显示它卡在获取mutexB的调用上
- 线程2的调用栈显示它卡在获取mutexA的调用上
- 通过互斥量状态可以确认:
- 线程1持有mutexA,请求mutexB
- 线程2持有mutexB,请求mutexA
- 形成循环等待,导致死锁
解决方案:
- 统一锁的获取顺序(两个线程都先获取mutexA再获取mutexB)
- 使用std::lock()原子地获取多个锁:
std::lock(mutexA, mutexB);
std::lock_guard<std::mutex> lockA(mutexA, std::adopt_lock);
std::lock_guard<std::mutex> lockB(mutexB, std::adopt_lock);
具体信息解读
(gdb) b thread1 # 在thread1入口设置断点
b thread2 # 在thread2入口设置断点
b lock.cpp:18 # 在获取mutexB的位置设置断点
b lock.cpp:30 # 在获取mutexA的位置设置断点
No source file named thread1 # 在thread1入口设置断点
b thread2 # 在thread2入口设置断点
b lock.cpp.
# 当你在使用gdb调试程序并尝试设置断点时,提示“Make breakpoint pending on future shared library load? (y or [n])”是因为你设置的断点所在的函数或代码片段属于动态加载的共享库(shared library),而该共享库尚未加载。
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) r
Starting program: /home/haonan/share/testCode/test_bug/lock
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7600640 (LWP 15181)]
[New Thread 0x7ffff6c00640 (LWP 15182)]
^C
Thread 1 "lock" received signal SIGINT, Interrupt.
__futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=15181, futex_word=0x7ffff7600910) at ./nptl/futex-internal.c:57
57 ./nptl/futex-internal.c: No such file or directory.
(gdb) info threads
Id Target Id Frame
# main
* 1 Thread 0x7ffff7ea23c0 (LWP 15178) "lock" __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265,
expected=15181, futex_word=0x7ffff7600910) at ./nptl/futex-internal.c:57
# thread1() 卡在了mutex
2 Thread 0x7ffff7600640 (LWP 15181) "lock" futex_wait (private=0, expected=2, futex_word=0x55555555a1a0 <mutex>)
at ../sysdeps/nptl/futex-internal.h:146
# thread2() 卡在了mutexA
3 Thread 0x7ffff6c00640 (LWP 15182) "lock" futex_wait (private=0, expected=2, futex_word=0x55555555a160 <mutexA>)
at ../sysdeps/nptl/futex-internal.h:146
# 切换到线程2
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff7600640 (LWP 15181))]
#0 futex_wait (private=0, expected=2, futex_word=0x55555555a1a0 <mutex>) at ../sysdeps/nptl/futex-internal.h:146
146 ../sysdeps/nptl/futex-internal.h: No such file or directory.
(gdb) bt
#0 futex_wait (private=0, expected=2, futex_word=0x55555555a1a0 <mutex>) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait (futex=futex@entry=0x55555555a1a0 <mutex>, private=0) at ./nptl/lowlevellock.c:49
#2 0x00007ffff7898002 in lll_mutex_lock_optimized (mutex=0x55555555a1a0 <mutex>) at ./nptl/pthread_mutex_lock.c:48
#3 ___pthread_mutex_lock (mutex=0x55555555a1a0 <mutex>) at ./nptl/pthread_mutex_lock.c:93
#4 0x00005555555556b7 in __gthread_mutex_lock (__mutex=0x55555555a1a0 <mutex>)
at /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749
#5 0x000055555555582e in std::mutex::lock (this=0x55555555a1a0 <mutex>) at /usr/include/c++/11/bits/std_mutex.h:100
#6 0x00005555555558a8 in std::lock_guard<std::mutex>::lock_guard (this=0x7ffff75ffd70, __m=...)
at /usr/include/c++/11/bits/std_mutex.h:229
# ---------------------------------------------------------------
# thread1() 在 lock.cpp:19 处,尝试获取 mutex(假设是 mutexA)。
# 但它被阻塞,说明 mutexA 已被其他线程持有。
#7 0x00005555555553c2 in thread1 () at lock.cpp:19
#8 0x0000555555556487 in std::__invoke_impl<void, void (*)()> (__f=@0x55555556ceb8: 0x555555555354 <thread1()>)
at /usr/include/c++/11/bits/invoke.h:61
#9 0x0000555555556433 in std::__invoke<void (*)()> (__fn=@0x55555556ceb8: 0x555555555354 <thread1()>)
at /usr/include/c++/11/bits/invoke.h:96
#10 0x00005555555563d4 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55555556ceb8)
at /usr/include/c++/11/bits/std_thread.h:259
#11 0x00005555555563a4 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55555556ceb8)
at /usr/include/c++/11/bits/std_thread.h:266
--Type <RET> for more, q to quit, c to continue without paging--
#12 0x0000555555556384 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55555556ceb0)
at /usr/include/c++/11/bits/std_thread.h:211
#13 0x00007ffff7cdc253 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#14 0x00007ffff7894ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#15 0x00007ffff7926850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
(gdb)
(gdb) thread 1
[Switching to thread 1 (Thread 0x7ffff7ea23c0 (LWP 15178))]
#0 __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=15181, futex_word=0x7ffff7600910)
at ./nptl/futex-internal.c:57
57 ./nptl/futex-internal.c: No such file or directory.
(gdb) bt
#0 __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=15181, futex_word=0x7ffff7600910)
at ./nptl/futex-internal.c:57
#1 __futex_abstimed_wait_common (cancel=true, private=128, abstime=0x0, clockid=0, expected=15181, futex_word=0x7ffff7600910)
at ./nptl/futex-internal.c:87
#2 __GI___futex_abstimed_wait_cancelable64 (futex_word=futex_word@entry=0x7ffff7600910, expected=15181, clockid=clockid@entry=0,
abstime=abstime@entry=0x0, private=private@entry=128) at ./nptl/futex-internal.c:139
#3 0x00007ffff7896624 in __pthread_clockjoin_ex (threadid=140737343653440, thread_return=0x0, clockid=0, abstime=0x0,
block=<optimized out>) at ./nptl/pthread_join_common.c:105
#4 0x00007ffff7cdc2c7 in std::thread::join() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00005555555555a8 in main () at lock.cpp:37
# 切换到线程3
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff6c00640 (LWP 15182))]
#0 futex_wait (private=0, expected=2, futex_word=0x55555555a160 <mutexA>) at ../sysdeps/nptl/futex-internal.h:146
146 ../sysdeps/nptl/futex-internal.h: No such file or directory.
(gdb) bt
#0 futex_wait (private=0, expected=2, futex_word=0x55555555a160 <mutexA>) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait (futex=futex@entry=0x55555555a160 <mutexA>, private=0) at ./nptl/lowlevellock.c:49
#2 0x00007ffff7898002 in lll_mutex_lock_optimized (mutex=0x55555555a160 <mutexA>) at ./nptl/pthread_mutex_lock.c:48
#3 ___pthread_mutex_lock (mutex=0x55555555a160 <mutexA>) at ./nptl/pthread_mutex_lock.c:93
#4 0x00005555555556b7 in __gthread_mutex_lock (__mutex=0x55555555a160 <mutexA>)
at /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749
#5 0x000055555555582e in std::mutex::lock (this=0x55555555a160 <mutexA>) at /usr/include/c++/11/bits/std_mutex.h:100
#6 0x00005555555558a8 in std::lock_guard<std::mutex>::lock_guard (this=0x7ffff6bffd70, __m=...)
at /usr/include/c++/11/bits/std_mutex.h:229
# -----------------------------------------------------------------
# thread2() 在 lock.cpp:28 处,尝试获取 mutexA。
# 但 mutexA 已被 thread1() 持有,而 thread1() 在等另一个 mutex。
#7 0x00005555555554c2 in thread2 () at lock.cpp:28
#8 0x0000555555556487 in std::__invoke_impl<void, void (*)()> (__f=@0x55555556d008: 0x555555555454 <thread2()>)
at /usr/include/c++/11/bits/invoke.h:61
#9 0x0000555555556433 in std::__invoke<void (*)()> (__fn=@0x55555556d008: 0x555555555454 <thread2()>)
at /usr/include/c++/11/bits/invoke.h:96
#10 0x00005555555563d4 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55555556d008)
at /usr/include/c++/11/bits/std_thread.h:259
#11 0x00005555555563a4 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55555556d008)
at /usr/include/c++/11/bits/std_thread.h:266
#12 0x0000555555556384 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55555556d000)
at /usr/include/c++/11/bits/std_thread.h:211
#13 0x00007ffff7cdc253 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#14 0x00007ffff7894ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
--Type <RET> for more, q to quit, c to continue without paging--
#15 0x00007ffff7926850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
当你在使用gdb调试程序并尝试设置断点时,提示“Make breakpoint pending on future shared library load? (y or [n])”是因为你设置的断点所在的函数或代码片段属于动态加载的共享库(shared library),而该共享库尚未加载。
以下是详细的中文解释和选择指南:
问题的含义:
共享库(Shared Library) 是一种动态加载的库,通常在程序运行过程中才加载。
当你设置断点时,如果共享库还未加载,gdb无法立即将断点绑定到代码中,因此会询问你是否希望将断点设置为“pending”(挂起状态)。
选项说明:
输入y(yes):
- 设置“挂起的断点”,即断点暂时等待,直到共享库加载后自动激活。
- 如果你确定共享库会在程序运行过程中加载,请选择y。
输入n(no):
- 不设置断点。你需要在共享库实际加载后再手动设置断点。
- 如果你不确定共享库是否会加载,或者需要立即检查其他部分代码,则选择n。
如何选择:
- 如果你调试的目标是共享库中的代码,选择y。
- 例如,程序运行过程中会加载共享库,并且你希望调试其中的函数,可以选择y。
- 如果你只是调试主程序或其他部分代码,选择n。