2017-09-22 19 views
5

Đ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 gccclang, 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.

Trả lời

3

Đoán của bạn có vẻ chính xác. Từ libgcc/gthr-posix.h tập tin trong kho lưu trữ nguồn gcc của (https://github.com/gcc-mirror/gcc.git):

/* For a program to be multi-threaded the only thing that it certainly must 
    be using is pthread_create. However, there may be other libraries that 
    intercept pthread_create with their own definitions to wrap pthreads 
    functionality for some purpose. In those cases, pthread_create being 
    defined might not necessarily mean that libpthread is actually linked 
    in. 

    For the GNU C library, we can use a known internal name. This is always 
    available in the ABI, but no other library would define it. That is 
    ideal, since any public pthread function might be intercepted just as 
    pthread_create might be. __pthread_key_create is an "internal" 
    implementation symbol, but it is part of the public exported ABI. Also, 
    it's among the symbols that the static libpthread.a always links in 
    whenever pthread_create is used, so there is no danger of a false 
    negative result in any statically-linked, multi-threaded program. 

    For others, we choose pthread_cancel as a function that seems unlikely 
    to be redefined by an interceptor library. The bionic (Android) C 
    library does not provide pthread_cancel, so we do use pthread_create 
    there (and interceptor libraries lose). */ 

#ifdef __GLIBC__ 
__gthrw2(__gthrw_(__pthread_key_create), 
    __pthread_key_create, 
    pthread_key_create) 
# define GTHR_ACTIVE_PROXY __gthrw_(__pthread_key_create) 
#elif defined (__BIONIC__) 
# define GTHR_ACTIVE_PROXY __gthrw_(pthread_create) 
#else 
# define GTHR_ACTIVE_PROXY __gthrw_(pthread_cancel) 
#endif 

static inline int 
__gthread_active_p (void) 
{ 
    static void *const __gthread_active_ptr 
    = __extension__ (void *) &GTHR_ACTIVE_PROXY; 
    return __gthread_active_ptr != 0; 
} 

Sau đó, trong suốt thời gian còn lại của tập tin rất nhiều các API pthread được gói bên trong kiểm tra với __gthread_active_p() chức năng. Nếu __gthread_active_p() trả lại 0 không có gì được thực hiện và thành công được trả về.

+0

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

+0

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. –

+0

Đ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

Các vấn đề liên quan