现在的位置: 首页 > 黄专家专栏 > 正文

原子操作浅谈

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)

赋值的汇编指令只有一条,说明在不对齐情况下,最后会生成多条机器指令

抱歉!评论已关闭.