为何两个std::unique_lock可同时持有同一std::mutex锁?
应用出现阻塞问题:两个独立线程中的不同std::unique_lock实例,owns_lock()均返回true,这与std::mutex的排他性矛盾。通过GDB调试发现:两个锁的_M_owns属性均为true,但关联的std::mutex的__lock、__count、__owner属性均为0,仅__nusers为2。
调试细节
线程信息
(gdb) info thread Id Target Id Frame * 1 Thread 0x7ffff7e897c0 (LWP 66233) "tests" __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x612000002f28) at ./nptl/futex-internal.c:57 33 Thread 0x7fffe5ee0640 (LWP 66269) "tests" __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x612000002f28) at ./nptl/futex-internal.c:57
共享资源与锁实例地址
线程1和33共享类成员vec、m、cv,各自拥有独立的std::unique_lock实例:
# 线程1的地址信息 (gdb) select 5 (gdb) p &m $14 = (std::mutex *) 0x612000002ed8 (gdb) p &cv $15 = (std::condition_variable *) 0x612000002f00 (gdb) p &vec $16 = (std::__debug::vector<std::shared_future<void>, std::allocator<std::shared_future<void> > > *) 0x612000002fb0 (gdb) p &l $17 = (std::unique_lock<std::mutex> *) 0x7fffffffd240 # 线程33的地址信息 (gdb) t 33 [Switching to thread 33 (Thread 0x7fffe5ee0640 (LWP 67477))] #0 __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x612000002f28) at ./nptl/futex-internal.c:57 57 in ./nptl/futex-internal.c (gdb) select 5 (gdb) p &m $18 = (std::mutex *) 0x612000002ed8 (gdb) p &cv $19 = (std::condition_variable *) 0x612000002f00 (gdb) p &vec $20 = (std::__debug::vector<std::shared_future<void>, std::allocator<std::shared_future<void> > > *) 0x612000002fb0 (gdb) p &l $21 = (std::unique_lock<std::mutex> *) 0x7fffe5edf8c0
阻塞位置
- 线程1阻塞在第94行:
cv.wait(l); - 线程33阻塞在第54行:
cv.wait(l);
锁与mutex状态
线程1的锁实例与mutex状态:
(gdb) t 1 [Switching to thread 1 (Thread 0x7ffff7e897c0 (LWP 67445))] #0 __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x612000002f28) at ./nptl/futex-internal.c:57 57 ./nptl/futex-internal.c: No such file or directory. (gdb) select 5 (gdb) p l $22 = {_M_device = 0x612000002ed8, _M_owns = true} (gdb) p m $23 = {<std::__mutex_base> = {_M_mutex = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 2, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = '\000' <repeats 12 times>, "\002", '\000' <repeats 26 times>, __align = 0}}, <No data fields>}
线程33的锁实例与mutex状态:
(gdb) t 33 [Switching to thread 33 (Thread 0x7fffe5ee0640 (LWP 67477))] #0 __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x612000002f28) at ./nptl/futex-internal.c:57 57 in ./nptl/futex-internal.c (gdb) select 5 (gdb) p l $24 = {_M_device = 0x612000002ed8, _M_owns = true} (gdb) p m $25 = {<std::__mutex_base> = {_M_mutex = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 2, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = '\000' <repeats 12 times>, "\002", '\000' <repeats 26 times>, __align = 0}}, <No data fields>}
疑问:为何两个std::unique_lock实例可同时显示持有锁(l.owns_lock() == true),而关联的std::mutex实际未被锁定?
这是std::condition_variable::wait的正常行为,从调试信息看两个线程都处于wait调用中,核心逻辑如下:
wait的执行流程:调用cv.wait(l)时,会自动完成两个关键操作:- 释放持有的
std::mutex(对应mutex的__lock变为0、__owner清零) - 阻塞等待条件变量的通知
此时
std::unique_lock的_M_owns标记不会立即修改——这是逻辑层面的持有标记,并非mutex实际状态的实时映射。wait内部保留这个标记,是为了在被唤醒时自动重新获取mutex,确保唤醒后线程能恢复持有锁的状态。- 释放持有的
mutex的
__nusers字段含义:__nusers=2仅表示当前有两个std::unique_lock实例关联到该mutex,和两个线程各自持有一个lock实例的情况完全吻合,不代表同时持有锁。状态矛盾的解释:此时两个线程都处于等待唤醒的状态,mutex未被任何线程持有,但
unique_lock的owns_lock()返回true是wait调用的约定——它保证调用wait前线程持有锁,唤醒后线程会重新持有锁,中间的释放和重新获取是wait内部的原子操作,unique_lock的标记维持true是为了封装这个过程,避免外部代码误解锁的状态。
总结:这种状态是condition_variable::wait执行过程中的中间状态,完全符合C++标准库的行为逻辑,并未违反mutex的排他性。当任意线程被唤醒时,会先重新获取mutex(此时mutex的__lock和__owner会更新为对应线程的信息),unique_lock的标记依然保持true,逻辑与实际状态再次一致。
内容的提问来源于stack exchange,提问作者norritt

