在 x86-64
机器上,int64
的读写操作是否具有原子性?
假设变量 val
是 int64
, 现在有两个线程,线程 writer
只负责向 val
写入数据,线程 reader
只负从 val
读取数据
假设 val
当前值为 0x0102030405060708
writer
线程正在执行写操作,将数据 0x0000111100002222
写入 val
reader
线程正在执行读操作,从 val
读取数据
那么 reader
线程得到的 val
只能是 0x0102030405060708
或 0x0000111100002222
还是某个不确定的中间状态?
如果 int64
的读写操作具有原子性,那么 reader
线程得到的只能是 0x0102030405060708
或 0x0000111100002222
如果 int64
的读写操作不具有原子性,那么 reader
线程得到可能是中间某个不确定的状态,比如前一半是 writer
正在写入的数据,后一半是旧的数据 0x0000111105060708
测试程序如下:
- 写入
val
的值一定是符合奇偶校验的 - 开启
10
个线程同时向val
输入数据 - 读线程连续的从
val
读取数据,并检查是否符合奇偶校验 - 如果
int64
的读写操作具备原子性,读线程得到的数据一定是符合奇偶校验的
测试发现,在 x86 64
机器上, int64
的读写操作确实具备原子性
go
语言测试程序
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
51
52
53
54
package main
import (
"log"
"math/rand"
"sync"
)
func check(v int64) bool {
var num int = 0
for i := 0; i < 64; i++ {
if v&0x01 != 0 {
num++
}
v = v >> 1
}
return num%2 == 0
}
func write(p *int64) {
v := rand.Int63()
if check(v) {
v = v ^ 0x01
}
*p = v
}
func main() {
var wg sync.WaitGroup
var val int64
write(&val)
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10000000; i++ {
write(&val)
}
}()
}
go func() {
for {
v := val
if check(v) {
log.Fatalf("atomic test faile, val = %d", v)
}
}
}()
wg.Wait()
log.Printf("test finished")
}
c++
测试程序
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <thread>
#include <random>
#include <iostream>
#include <atomic>
#include <cstdlib>
bool inline check(uint64_t val){
int num = 0;
for(int i=0;i<sizeof(val*8);i++){
if(val &0x01) num++;
val = val >> 1;
}
return num%2;
}
void inline write(std::mt19937_64 *g64, uint64_t *ptr){
uint64_t val = (*g64)();
if(check(val)) val = val ^ 0x01;
*ptr = val;
// std::cout << val << std::endl;
}
std::pair<uint64_t,bool> inline read(uint64_t *ptr){
uint64_t val = *ptr;
return std::make_pair(val,check(val));
}
int main()
{
std::mt19937_64 g64;
uint64_t val[2];
std::atomic<bool> flag(false);
auto p1 = reinterpret_cast<uint8_t*>(val);
p1 = p1+3;
auto ptr = reinterpret_cast<uint64_t*>(p1);
write(&g64,ptr);
auto checkVal = read(ptr);
std::cout << "start, val = " << std::hex << checkVal.first << ", check = " << checkVal.second << std::endl;
std::thread wt([](std::mt19937_64 *g64, uint64_t *ptr,std::atomic<bool> *flag){
for(int i=0;i<100000000;i++){
write(g64,ptr);
}
*flag = true;
}, &g64,ptr,&flag);
std::thread rt([](uint64_t *ptr,std::atomic<bool> *flag){
while(*flag==false){
auto val = read(ptr);
if(val.second){
std::cout << "atomic failed, val = " << std::hex << val.first << ", check = " << val.second << std::endl;
std::exit(-1);
}
}
},ptr,&flag);
wt.join();
rt.join();
return 0;
}