Đi chức năng này đơn giản mà increments một số nguyên dưới một khóa thực hiện bởi std::mutex
:Tại sao các hàm sử dụng std :: mutex kiểm tra địa chỉ của pthread_key_create?
#include <mutex>
std::mutex m;
void inc(int& i) {
std::unique_lock<std::mutex> lock(m);
i++;
}
Tôi mong chờ này (sau khi nội tuyến) để biên dịch một cách đơn giản để một cuộc gọi của m.lock()
một tăng của i
và sau đó m.unlock()
.
Kiểm tra hội đồng được tạo cho các phiên bản gần đây của gcc
và clang
, tuy nhiên, chúng tôi thấy có thêm một biến chứng. Sử dụng phiên bản gcc
trước tiên:
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)
Đây là hai dòng đầu tiên thú vị. Mã được lắp ráp kiểm tra địa chỉ của __gthrw___pthread_key_create
(đó là việc triển khai cho pthread_key_create
- một chức năng để tạo khóa lưu trữ cục bộ), và nếu nó bằng 0, nó chi nhánh đến .L2
thực hiện số gia tăng trong một lệnh đơn mà không cần khóa bất kỳ tất cả các.
Nếu nó khác 0, nó tiến hành như mong đợi: khóa mutex, thực hiện gia tăng và mở khóa.
clang
làm nhiều hơn: nó kiểm tra địa chỉ của hàm hai lần, một lần trước lock
và một lần trước khi 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)
mục đích của việc kiểm tra này là gì?
Có lẽ đó là hỗ trợ trường hợp tệp đối tượng cuối cùng được tuân thủ thành một tệp nhị phân không hỗ trợ pthread và sau đó quay lại phiên bản mà không khóa trong trường hợp đó? Tôi không thể tìm thấy bất kỳ tài liệu nào về hành vi này.
Cảm ơn câu trả lời tuyệt vời. Bạn cũng trả lời câu hỏi thứ hai (không có) của tôi là hành vi này đã được trình biên dịch thực hiện hay không "nhận thức" các phương thức sử dụng pthreads và biên dịch các phương thức sử dụng chúng đặc biệt, hoặc kiểm tra thực sự là nguồn glibc/pthreads (đó là sau này). Điều này cũng giải thích tại sao 'clang' có hai kiểm tra: hai kiểm tra là _default_ nhưng gcc chỉ quản lý để kết hợp các kiểm tra thành một, về cơ bản biên dịch hai phiên bản khác nhau của phương thức, trong khi' clang' không thể kết hợp các kiểm tra, tại ít nhất là tại '-O2'. – BeeOnRope
Tôi không phải là tất cả những người quen thuộc với GCC internals, nhưng có vẻ như điều này được thực hiện không có trong glibc/pthreads nhưng trong [gcclib] (https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html) "thư viện thời gian chạy cấp thấp" được trình biên dịch sử dụng. Có vẻ như libgcc có thể có các phụ thuộc vào libc đang được sử dụng, điều mà tôi không mong đợi. Nhưng có thể là tôi không hoàn toàn hiểu những gì đang xảy ra. –
Điểm tốt, dựa trên giao diện nhanh của tôi, có vẻ như nó nằm trong thư viện hỗ trợ 'libgcc' chứ không phải trong' glibc' thích hợp, điều đó có nghĩa là nó có khả năng bị ràng buộc chặt chẽ hơn với trình biên dịch. – BeeOnRope