把这个简单的功能,通过std::mutex
实施了锁下增加一个整数:为什么使用std :: mutex的函数对pthread_key_create的地址进行空检查?
#include <mutex>
std::mutex m;
void inc(int& i) {
std::unique_lock<std::mutex> lock(m);
i++;
}
我希望这(内联后),一个简单的方法来编译成的m.lock()
呼叫i
然后m.unlock()
增量。
检查生成的程序集最近的版本gcc
和clang
,但是,我们看到一个额外的复杂性。服用gcc
版本第一:
inc(int&):
mov eax, OFFSET FLAT:__gthrw___pthread_key_create(unsigned int*, void (*)(void*))
test rax, rax
je .L2
push rbx
mov rbx, rdi
mov edi, OFFSET FLAT:m
call __gthrw_pthread_mutex_lock(pthread_mutex_t*)
test eax, eax
jne .L10
add DWORD PTR [rbx], 1
mov edi, OFFSET FLAT:m
pop rbx
jmp __gthrw_pthread_mutex_unlock(pthread_mutex_t*)
.L2:
add DWORD PTR [rdi], 1
ret
.L10:
mov edi, eax
call std::__throw_system_error(int)
这是第一对夫妇是有趣的线。汇编后的代码检查地址__gthrw___pthread_key_create
(这是pthread_key_create
的实现 - 创建线程本地存储密钥的函数),如果它为零,则分支到.L2
,它在单个指令中实现增量,而不锁定所有。
如果它不为零,则按预期进行:锁定互斥锁,执行增量和解锁。
clang
确实更:它检查功能的地址两次,在lock
之前,其unlock
一次一次:
inc(int&): # @inc(int&)
push rbx
mov rbx, rdi
mov eax, __pthread_key_create
test rax, rax
je .LBB0_4
mov edi, m
call pthread_mutex_lock
test eax, eax
jne .LBB0_6
inc dword ptr [rbx]
mov eax, __pthread_key_create
test rax, rax
je .LBB0_5
mov edi, m
pop rbx
jmp pthread_mutex_unlock # TAILCALL
.LBB0_4:
inc dword ptr [rbx]
.LBB0_5:
pop rbx
ret
.LBB0_6:
mov edi, eax
call std::__throw_system_error(int)
这是什么检查的目的是什么?
也许是为了支持目标文件最终被编译到二进制文件而没有pthreads支持的情况下,然后在没有锁定的情况下回退到一个版本的情况?我无法找到有关此行为的任何文档。
伟大的答案谢谢。您还回答了我的(未说明的)第二个问题,即是否这种行为是由编译器实现的,它是“知道”pthreads的 - 使用方法并编译专门使用它们的方法,或者检查实际上是否在glibc/pthreads源代码中(这是后者)。这也解释了为什么'clang'有两个检查:两个检查是_default_,但gcc只是设法将检查合并为一个,本质上是编译两个不同版本的方法,而'clang'不能组合检查,在至少在'-O2'处。 – BeeOnRope
我并不是所有人都熟悉GCC内部,但它看起来像不是在glibc/pthreads中实现,而是在[gcclib](https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html)中实现,它是编译器使用的“低级运行时库”。它看起来像libgcc可能依赖于正在使用的libc,我没有想到。但可能是因为我不完全理解发生了什么。 –
好的一点,根据我的快速查看,它看起来好像是在'libgcc'支持库中,而不是'glibc'正确,这意味着它可能更紧密地绑定到编译器。 – BeeOnRope