【GDB调试-3】多线程死锁

一、进程和线程

进程:

  • 程序加载到内存后得到进程
  • 程序中某个数据集合的一次运行活动

线程:

  • 进程中的一个执行单元
  • 操作系统中一个可以调度的实体
  • 进程中一个相对独立的控制流序列

两者联系:

  • 系统分配资源的基本单位是进程,但是CPU调度的基本单位是线程
  • 进程中可以存在多个线程共享进程资源
  • 线程不能脱离进程单独存在,只能依赖于进程执行
  • 任意线程都可以创建其他新的线程
  • 线程有生命周期,有诞生和死亡

完成一件复杂的工作,单线程已经无法满足需求,我们是应该首选多线程还是多进程呢?进程使用的资源要比线程多得多,系统每启动一个线程都需要重新分配相应的资源,会占用很多内存。相比之下使用线程的开销就会小很多,只有一个线程对象和对应的堆栈。因此为了更加有效的工作,程序往往会启动多个线程进行工作,实现的功能越复杂,使用的线程数量也就越多,在多线程中进程资源共享,当访问这些资源时候就一定会遇到同步和互斥的问题,死锁就是由互斥产生的问题。

二、死锁

死锁概念

  • 线程之间相互等待临界资源而造成彼此无法继续执行的情况。

死锁产生条件

  • 系统中存在多个临界资源并且资源不能被抢占
  • 线程需要多个资源才能继续执行

临界资源

  • 一次只能被一个线程使用的资源

不能被抢占

  • 当一个线程持有该资源的时候,如果该线程没有主动释放资源,其他线程无法迫使其强行释放而进行资源抢占。

三、死锁检测

3.1、创建死锁程序

编写程序创建了两个线程,按照上面产生的条件使用两把互斥锁对临界资源进行访问,每个线程中交替获取锁,然后释放:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex g_mutex1;
std::mutex g_mutex2;

void thread1()
{
    while(1)
    {
        g_mutex1.lock();
        g_mutex2.lock();

        std::cout << "thread1 do work ..." << std::endl;

        g_mutex2.unlock();
        g_mutex1.unlock();
    }
}

void thread2()
{
    while(1)
    {
        g_mutex2.lock();
        g_mutex1.lock();

        std::cout << "thread2 do work ..." << std::endl;

        g_mutex1.unlock();
        g_mutex2.unlock();
    }
}

int main()
{
    std::thread t1(thread1);
    std::thread t2(thread2);

    t1.join();
    t2.join();
    return 0;
}

编译

g++ -g main.cpp -lpthread

运行结果显示只有thread1运行,并且一段时间后停止,这是由于t1对象先进行创建,导致thread1先进行运行,thread2运行后相互等待对方释放资源,最终导致死锁。
在这里插入图片描述

3.2、使用GDB分析死锁

程序产生死锁的时间具有不确定性,如果重新运行程序,错误可能很难再捕捉,因此可以采用gdb动态链接到目标进程。

查看进程号

ps -aux | grep a.out

启动gdb

sudo gdb

链接目标程序,进入后程序会被暂停

attach 进程号

查看所有进程当前状态

(gdb) info threads
  Id   Target Id                                Frame 
* 1    Thread 0x7f34d8997740 (LWP 8915) "a.out" __pthread_clockjoin_ex (threadid=139864948958976, thread_return=0x0, 
    clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145
  2    Thread 0x7f34d8996700 (LWP 8916) "a.out" __lll_lock_wait (futex=futex@entry=0x55dc8faa11a0 <g_mutex2>, private=0)
    at lowlevellock.c:52
  3    Thread 0x7f34d8195700 (LWP 8917) "a.out" __lll_lock_wait (futex=futex@entry=0x55dc8faa1160 <g_mutex1>, private=0)
    at lowlevellock.c:52

分别查看栈回溯

thread apply all bt

Thread 3 (Thread 0x7f27ca9ef700 (LWP 10122)):
#0  __lll_lock_wait (futex=futex@entry=0x560a953c6160 <g_mutex1>, private=0) at lowlevellock.c:52
#1  0x00007f27cb7400a3 in __GI___pthread_mutex_lock (mutex=0x560a953c6160 <g_mutex1>) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000560a953c3541 in __gthread_mutex_lock (__mutex=0x560a953c6160 <g_mutex1>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749
#3  0x0000560a953c3596 in std::mutex::lock (this=0x560a953c6160 <g_mutex1>) at /usr/include/c++/9/bits/std_mutex.h:100
#4  0x0000560a953c33aa in thread2 () at main.cpp:27
#5  0x0000560a953c3ebe in std::__invoke_impl<void, void (*)()> (__f=@0x560a962d4008: 0x560a953c338a <thread2()>) at /usr/include/c++/9/bits/invoke.h:60
#6  0x0000560a953c3e56 in std::__invoke<void (*)()> (__fn=@0x560a962d4008: 0x560a953c338a <thread2()>) at /usr/include/c++/9/bits/invoke.h:95
#7  0x0000560a953c3de8 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x560a962d4008) at /usr/include/c++/9/thread:244
#8  0x0000560a953c3da5 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x560a962d4008) at /usr/include/c++/9/thread:251
#9  0x0000560a953c3d76 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x560a962d4000) at /usr/include/c++/9/thread:195
#10 0x00007f27cb628de4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007f27cb73d609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#12 0x00007f27cb467293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 2 (Thread 0x7f27cb1f0700 (LWP 10121)):
#0  __lll_lock_wait (futex=futex@entry=0x560a953c61a0 <g_mutex2>, private=0) at lowlevellock.c:52
#1  0x00007f27cb7400a3 in __GI___pthread_mutex_lock (mutex=0x560a953c61a0 <g_mutex2>) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000560a953c3541 in __gthread_mutex_lock (__mutex=0x560a953c61a0 <g_mutex2>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749
#3  0x0000560a953c3596 in std::mutex::lock (this=0x560a953c61a0 <g_mutex2>) at /usr/include/c++/9/bits/std_mutex.h:100
#4  0x0000560a953c3348 in thread1 () at main.cpp:13
#5  0x0000560a953c3ebe in std::__invoke_impl<void, void (*)()> (__f=@0x560a962d3eb8: 0x560a953c3328 <thread1()>) at /usr/include/c++/9/bits/invoke.h:60
#6  0x0000560a953c3e56 in std::__invoke<void (*)()> (__fn=@0x560a962d3eb8: 0x560a953c3328 <thread1()>) at /usr/include/c++/9/bits/invoke.h:95
#7  0x0000560a953c3de8 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x560a962d3eb8) at /usr/include/c++/9/thread:244
#8  0x0000560a953c3da5 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x560a962d3eb8) at /usr/include/c++/9/thread:251
#9  0x0000560a953c3d76 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x560a962d3eb0) at /usr/include/c++/9/thread:195
#10 0x00007f27cb628de4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007f27cb73d609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#12 0x00007f27cb467293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 1 (Thread 0x7f27cb1f1740 (LWP 10120)):
#0  __pthread_clockjoin_ex (threadid=139808888260352, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145
#1  0x00007f27cb629047 in std::thread::join() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x0000560a953c343a in main () at main.cpp:41                              

最终确定程序的确发生了死锁

3.3、使用core dump文件分析死锁

当无法进行直接调试的时候,尤其是部署到现场之后,可以委托客户生成一个core dump文件用于分析问题。

查看进程号

ps -aux | grep a.out

启动gdb

sudo gdb

链接目标程序,进入后程序会被暂停

attach 进程号

生成core dump文件

gcore 保存文件名字

等待客户提交了文件后进行调试

gcore 可执行程序名字 core-dump文件名

结果

(gdb) thread apply all bt

Thread 3 (Thread 0x7f12864d0700 (LWP 10871)):
#0  __lll_lock_wait (futex=futex@entry=0x560150530160 <g_mutex1>, private=0) at lowlevellock.c:52
#1  0x00007f12872210a3 in __GI___pthread_mutex_lock (mutex=0x560150530160 <g_mutex1>) at ../nptl/pthread_mutex_lock.c:80
#2  0x000056015052d541 in __gthread_mutex_lock (__mutex=0x560150530160 <g_mutex1>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749
#3  0x000056015052d596 in std::mutex::lock (this=0x560150530160 <g_mutex1>) at /usr/include/c++/9/bits/std_mutex.h:100
#4  0x000056015052d3aa in thread2 () at main.cpp:27
#5  0x000056015052debe in std::__invoke_impl<void, void (*)()> (__f=@0x56015130c008: 0x56015052d38a <thread2()>) at /usr/include/c++/9/bits/invoke.h:60
#6  0x000056015052de56 in std::__invoke<void (*)()> (__fn=@0x56015130c008: 0x56015052d38a <thread2()>) at /usr/include/c++/9/bits/invoke.h:95
#7  0x000056015052dde8 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x56015130c008) at /usr/include/c++/9/thread:244
#8  0x000056015052dda5 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x56015130c008) at /usr/include/c++/9/thread:251
#9  0x000056015052dd76 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x56015130c000) at /usr/include/c++/9/thread:195
#10 0x00007f1287109de4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007f128721e609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#12 0x00007f1286f48293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 2 (Thread 0x7f1286cd1700 (LWP 10870)):
#0  __lll_lock_wait (futex=futex@entry=0x5601505301a0 <g_mutex2>, private=0) at lowlevellock.c:52
#1  0x00007f12872210a3 in __GI___pthread_mutex_lock (mutex=0x5601505301a0 <g_mutex2>) at ../nptl/pthread_mutex_lock.c:80
#2  0x000056015052d541 in __gthread_mutex_lock (__mutex=0x5601505301a0 <g_mutex2>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749
#3  0x000056015052d596 in std::mutex::lock (this=0x5601505301a0 <g_mutex2>) at /usr/include/c++/9/bits/std_mutex.h:100
#4  0x000056015052d348 in thread1 () at main.cpp:13
#5  0x000056015052debe in std::__invoke_impl<void, void (*)()> (__f=@0x56015130beb8: 0x56015052d328 <thread1()>) at /usr/include/c++/9/bits/invoke.h:60
#6  0x000056015052de56 in std::__invoke<void (*)()> (__fn=@0x56015130beb8: 0x56015052d328 <thread1()>) at /usr/include/c++/9/bits/invoke.h:95
#7  0x000056015052dde8 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x56015130beb8) at /usr/include/c++/9/thread:244
#8  0x000056015052dda5 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x56015130beb8) at /usr/include/c++/9/thread:251
#9  0x000056015052dd76 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x56015130beb0) at /usr/include/c++/9/thread:195
#10 0x00007f1287109de4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007f128721e609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#12 0x00007f1286f48293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 1 (Thread 0x7f1286cd2740 (LWP 10869)):
#0  __pthread_clockjoin_ex (threadid=139717547726592, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, bloc--Type <RET> for more, q to quit, c to continue without paging--
k=<optimized out>) at pthread_join_common.c:145
#1  0x00007f128710a047 in std::thread::join() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x000056015052d43a in main () at main.cpp:41

四、死锁的避免

按照死锁产生的条件,我们可以对其中一个或者多个进行破坏,那么就可以避免死锁

方法一

  • 对所有临界资源分配一个唯一的序号(r1, …rn)
  • 对应的资源锁也分配一个唯一的序号(m1,…mn)
  • 系统中所有程序严格按照递增的次序请求资源

方法二

  • 只使用一把线程锁就不会产生死锁,但是系统效率会降低(相当于只有一个临界资源)

方法三

  • 当获取某个资源失败的时候,释放手里已经持有的资源,释放资源后上下文状态可能还需要进行调整,可能得不偿失。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕咚.萌西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值