Posts int64的读写操作是否具有原子性质
Post
Cancel

int64的读写操作是否具有原子性质

x86-64 机器上,int64 的读写操作是否具有原子性?

假设变量 valint64, 现在有两个线程,线程 writer 只负责向 val 写入数据,线程 reader 只负从 val 读取数据

假设 val 当前值为 0x0102030405060708

writer 线程正在执行写操作,将数据 0x0000111100002222 写入 val

reader 线程正在执行读操作,从 val 读取数据

那么 reader 线程得到的 val 只能是 0x01020304050607080x0000111100002222 还是某个不确定的中间状态?

如果 int64 的读写操作具有原子性,那么 reader 线程得到的只能是 0x01020304050607080x0000111100002222

如果 int64 的读写操作不具有原子性,那么 reader 线程得到可能是中间某个不确定的状态,比如前一半是 writer 正在写入的数据,后一半是旧的数据 0x0000111105060708

测试程序如下:

  1. 写入 val 的值一定是符合奇偶校验的
  2. 开启 10 个线程同时向 val 输入数据
  3. 读线程连续的从 val 读取数据,并检查是否符合奇偶校验
  4. 如果 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;
}
This post is licensed under CC BY 4.0 by the author.