采用这个简单的函数,它在由实现的锁下递增一个整数std::mutex
:
#include <mutex>
std::mutex m;
void inc(int& i) {
std::unique_lock<std::mutex> lock(m);
i++;
}
我希望这(在内联之后)以一种直接的方式编译为调用thenm.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
做得更多:它检查函数的地址两次,一次在 the之前,一次在 thelock
之前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 支持的二进制文件,然后在这种情况下回退到没有锁定的版本的情况?我找不到有关此行为的任何文档。