自卷如何救,互斥量重入死锁例谈

文章讲述了开发者在调试NDB的Linux版本时遇到线程死锁问题,通过gdb追踪发现是由于Linux下pthread_mutex的非递归特性导致。作者分享了解决方案,即使用recursive_mutex类型,并在代码中进行相应设置,以确保线程安全并预告了即将举行的《内核夜话》活动。
摘要由CSDN通过智能技术生成

上周,小伙伴调试NDB的LINUX版本时遇到硬石头。开始调试后,NanoCode界面卡住不动了。

今天一早,我亲自上手,把gdb附加到NanoCode的extension Host进程,然后thread apply all bt看各个线程的状态。

很快,看到了问题。关键的调试引擎线程死锁了。 

#0  futex_wait (private=0, expected=2, futex_word=0x7f4c4b4448 <zk+144>) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait (futex=futex@entry=0x7f4c4b4448 <zk+144>, private=private@entry=0) at ./nptl/lowlevellock.c:49
#2  0x0000007f969569e4 in lll_mutex_lock_optimized (mutex=0x7f4c4b4448 <zk+144>) at ./nptl/pthread_mutex_lock.c:48
#3  ___pthread_mutex_lock (mutex=0x7f4c4b4448 <zk+144>) at ./nptl/pthread_mutex_lock.c:93
#4  0x0000007f4bee5fec in non-virtual thunk to dxer::GetCurrentProcessExecutableName(char*, unsigned int, unsigned int*) ()
    at ../elk/dxer.hpp:2510
#5  0x0000007f4c687e74 in NdBoss::UpdateCurDebuggee (this=0x3400ae6000) at ../ndi/NdBoss.cpp:3188
#6  0x0000007f4c68646c in NdBoss::CbEngineStateChange (this=0x3400ae6000, ulFlags=1, ul64Argument=0)
    at ../ndi/NdBoss.cpp:2338
#7  0x0000007f4c672ee0 in CEventCallbacks::ChangeEngineState (this=0x3400f4b780, Flags=1, Argument=0)
    at ../ndi/EventCallbacks.cpp:204
#8  0x0000007f4bee1c98 in NotifyChangeEngineState
#9  0x0000007f4bf17bc4 in zhuge::ready_to_break
    (this=this@entry=0x7f4c4a3d50 <zg>, ExtraStatusFlags=ExtraStatusFlags@entry=0) at ../elk/zhuge.cpp:499
#10 0x0000007f4bf08de0 in poll_meta (Flags=0, Timeout=4294967295) at ../elk/dxer_ctrl.cpp:3382
#11 0x0000007f4bf092b4 in non-virtual thunk to dxer::WaitForEvent(unsigned int, unsigned int) () at ../elk/dxer.hpp:851
#12 0x0000007f4c683e58 in NdBoss::SessionLoop (this=0x3400ae6000) at ../ndi/NdBoss.cpp:1112
#13 0x0000007f4c6831f4 in SessionThreadProc (lpParameter=0x3400ae6000) at ../ndi/NdBoss.cpp:763
#14 0x0000007f969537d0 in start_thread (arg=0x7f982d9780) at ./nptl/pthread_create.c:444
#15 0x0000007f969bf5cc in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone3.S:76

扫视一番调用栈的函数,卡在著名的lll(low level lock)函数。

切换到这个线程。

(gdb) thread 16

[Switching to thread 16 (Thread 0x7f4ba1ea80 (LWP 3891))]

再切换到2号栈帧。然后打印mutex对象。

(gdb) p mutex

$1 = (pthread_mutex_t *) 0x7f4c4b4448 <zk+144>

    因为是指针,所以需要加个星。    

(gdb) p *mutex
$2 = {__data = {__lock = 2, __count = 0,
__owner = 3891, __nusers = 1, __kind = 0, __spins = 0, __list = {__prev = 0x0,
__next = 0x0}},
__size = "\002\000\000\000\000\000\000\0003\017\000\000\001",
'\000' <repeats 34 times>, __align = 2}

    看关键的owner字段:__owner = 3891

线程ID是3891,居然是它自己。

843f1ac4aea1becdec58af781c4d6de0.jpeg

为什么会是这个线程自己呢?因为这个线程的父函数里已经获得这个互斥量,然后它的子函数又调用了需要加锁的函数,又获得这个互斥量。

从count字段为2也可以看出这个特征。

这个代码本来是在Windows下开发的,使用的是关键区做保护。在Windows下,同一个线程是可以多次进同一个关键区的,也就是所谓的可递归(重入)。

移植到Linux时,使用了pthread_mutex_t来替代关键区,但是Linux下的pthread_mutex_t默认是不可以递归的,也就是即使是同一个线程,也不允许第二次获得同一个互斥量。

如何解决这个问题呢?一种方法是使用C++标准库的recursive_mutex。另一种方法是修改pthread_mutex_t的类型,让它支持重入。我选择了第二种方法。

进一步说,pthread_mutex_t支持四种类型:

-  PTHREAD_MUTEX_TIMED_NP,这是默认值,也就是普通锁。

- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,这个行为与Windows是类似的。

- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型相同,为了保证不出现最简单情况下的死锁。

- PTHREAD_MUTEX_ADAPTIVE_NP,自适应锁。

因为pthread_mutex_init函数需要通过一个属性结构体来设置锁的类型,所以新的代码大致如下:

pthread_mutexattr_t ma;
  pthread_mutexattr_init(&ma);
  pthread_mutexattr_settype (&ma, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(&mutex, &ma);

这样修改后,问题解决了。

顺便预告一下,本周三晚上,有一期《内核夜话》,欢迎新老朋友来听。

3e956c0461c3544109d59dc5d3171647.png

(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)

*************************************************

正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生

扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物

97c74c586feaa1bfaec8aa94be46c340.png

也欢迎关注格友公众号

66803efd1893c01db4237e74476d406e.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值