把這個簡單的功能,通過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