現在的位置: 首頁 > 黃專家專欄 > 正文

原子操作淺談

2014年10月30日 黃專家專欄 ⁄ 共 2170字 ⁄ 字型大小 評論關閉

簡單說,所謂原子操作是指不會被打斷的操作,這種」打斷」在操作系統層面,一般是指線程間的上下文切換。假設,一個線程對一個共享的變數寫入一個 值,那麼另一個觀察這個變數的線程,要麼看到原值,要麼看到新值,不會看到一種中間狀態,這種中間狀態可以簡單理解為部分寫入(torn write)。

轉到程序設計層面,如果你用C/C++寫入或者讀取一個變數,這個操作是原子的嗎?

答案是:不一定。

假設我們有一個 64bit 的共享變數, store 函數表示寫入這個變數一個值

1
2
3
4
uint64 var;
void store() {
  var = 0x1000000000000001;
}

那麼 以上的操作,在 32-bit x86 架構上生成的彙編代碼可能如下:

1
2
3
mov  DWORD PTR var, 2
mov   DWORD PTR var + 4, 1
ret

明顯可以看出,這個賦值操作不是原子的。同樣的,我們可以得到讀這個 var 的操作也不是原子的。

但是,如果換成 uint32 呢?

1
2
3
4
5
uint32 foo;

void store() {
    foo = 0x80286;
}

如果這個 foo 對其到 32-bit 邊界,那麼操作是原子的,否則這個操作的原子性某些32-bit的平台不一定支持。 這個在 x64 架構同樣成立。我們看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>

#pragma pack(2)
struct ShardValue {
  char a[57];
  uint64_t b;
};

char a;
ShardValue var;

void* thread_worker(void* k) {
  uint64_t* v = (uint64_t*)k;
  while (1) {
      var.b = *v; 
  }
}

void* thread_observer(void* k) {
  uint64_t v;
  while(1) {
      v = var.b;
      if (v == 0x1111111100000001 ||
          v == 0x4444444400000001 ||
          v == 0x6666666600000001 ||
          v == 0x2222222200000001 ||
          v == 0x3333333300000001 ||
          v == 0x5555555500000001) {
      } else {
          printf("v : %p\n", v); 
          exit(0);
      }   
  }
}

int main() {
  pthread_t tid_1, tid_2, tid_3, tid_4, tid_5, tid_6;
  uint64_t v1 = 0x1111111100000001;
  uint64_t v2 = 0x2222222200000001;
  uint64_t v3 = 0x3333333300000001;
  uint64_t v4 = 0x4444444400000001;
  uint64_t v5 = 0x5555555500000001;
  uint64_t v6 = 0x6666666600000001;

  pthread_create(&tid_1, NULL, thread_worker, &v1);
  pthread_create(&tid_2, NULL, thread_worker, &v2);
  pthread_create(&tid_3, NULL, thread_worker, &v3);
  pthread_create(&tid_4, NULL, thread_worker, &v4);
  pthread_create(&tid_5, NULL, thread_worker, &v5);
  pthread_create(&tid_6, NULL, thread_worker, &v6);

  pthread_t o1, o2, o3, o4, o5, o6; 
  pthread_create(&o5, NULL, thread_observer, NULL);
  pthread_create(&o6, NULL, thread_observer, NULL);
  pthread_create(&o1, NULL, thread_observer, NULL);
  pthread_create(&o2, NULL, thread_observer, NULL);
  pthread_create(&o3, NULL, thread_observer, NULL);
  pthread_create(&o4, NULL, thread_observer, NULL);

  pthread_join(tid_1, NULL);
  pthread_join(tid_2, NULL);
  pthread_join(tid_3, NULL);
  pthread_join(tid_4, NULL);
  pthread_join(tid_5, NULL);
  pthread_join(tid_6, NULL);
}

測試機是 x86_64 Intel® Core™ i5 CPU 760 @ 2.80GHz 4核心

可以得出這樣的結果:

v : 0x5555111100000001

查看彙編指令

1
2
3
4
5
6
_Z13thread_workerPv:
......
.L2:
movq  -8(%rbp), %rax
movq  (%rax), %rax
movq  %rax, var+58(%rip)

賦值的彙編指令只有一條,說明在不對齊情況下,最後會生成多條機器指令

抱歉!評論已關閉.