Xem xét các mã đặc sau:Tại sao __sync_add_and_fetch hoạt động với biến 64 bit trên hệ thống 32 bit?
/* Compile: gcc -pthread -m32 -ansi x.c */
#include <stdio.h>
#include <inttypes.h>
#include <pthread.h>
static volatile uint64_t v = 0;
void *func (void *x) {
__sync_add_and_fetch (&v, 1);
return x;
}
int main (void) {
pthread_t t;
pthread_create (&t, NULL, func, NULL);
pthread_join (t, NULL);
printf ("v = %"PRIu64"\n", v);
return 0;
}
Tôi có một biến uint64_t
mà tôi muốn tăng nguyên tử, bởi vì các biến là một bộ đếm trong một chương trình đa luồng. Để đạt được nguyên tử, tôi sử dụng số atomic builtins của GCC.
Nếu tôi biên dịch cho một hệ thống amd64 (-m64) thì mã bộ mã hóa được tạo ra dễ hiểu. Bằng cách sử dụng lock addq
, bộ xử lý đảm bảo số gia tăng là nguyên tử.
400660: f0 48 83 05 d7 09 20 lock addq $0x1,0x2009d7(%rip)
Nhưng mã C cùng sản xuất một mã rất phức tạp ASM trên một hệ thống ia32 (-m32):
804855a: a1 28 a0 04 08 mov 0x804a028,%eax
804855f: 8b 15 2c a0 04 08 mov 0x804a02c,%edx
8048565: 89 c1 mov %eax,%ecx
8048567: 89 d3 mov %edx,%ebx
8048569: 83 c1 01 add $0x1,%ecx
804856c: 83 d3 00 adc $0x0,%ebx
804856f: 89 ce mov %ecx,%esi
8048571: 89 d9 mov %ebx,%ecx
8048573: 89 f3 mov %esi,%ebx
8048575: f0 0f c7 0d 28 a0 04 lock cmpxchg8b 0x804a028
804857c: 08
804857d: 75 e6 jne 8048565 <func+0x15>
Dưới đây là những gì tôi không hiểu:
lock cmpxchg8b
không đảm bảo rằng biến đã thay đổi chỉ được ghi nếu giá trị kỳ vọng vẫn nằm trong địa chỉ đích. Việc so sánh và trao đổi được đảm bảo xảy ra một cách nguyên tử.- Nhưng điều gì đảm bảo rằng việc đọc biến trong 0x804855a và 0x804855f là nguyên tử?
Có lẽ nó không quan trọng nếu có một "đọc bẩn", nhưng ai đó có thể xin phác thảo một đoạn ngắn bằng chứng rằng không có vấn đề?
Hơn nữa: Tại sao mã được tạo lại chuyển về 0x8048565 chứ không phải 0x804855a? Tôi tích cực rằng điều này chỉ đúng nếu các nhà văn khác, cũng chỉ tăng biến. Đây có phải là yêu cầu liên quan đến chức năng __sync_add_and_fetch
không?