C/C++中整形读写的原子性

2023-02-17  本文已影响0人  呆呆的张先生

并发程序编写时,对单读单写的数据,通常认为是可以不用加锁的,最多注意下内存屏障;

问题描述

常见的单独单写并发时脏读的问题:

分析

此部分代码是架构相关的,有Arm / X86 上迁移问题

violate

C/C++ 中的 volatile - 知乎 (zhihu.com)

volatile 不能解决多线程中的问题。
按照 Hans Boehm & Nick Maclaren 的总结volatile 只在三种场合下是合适的。

  • 和信号处理(signal handler)相关的场合;
  • 和内存映射硬件(memory mapped hardware)相关的场合;
  • 和非本地跳转(setjmplongjmp)相关的场合。

Intel spec

Intel® 64 and IA-32 Architectures Software Developer Manuals 卷3 - 8.1.1

PA问题中,地址对齐的访问都是原子的,同一缓存行内和不触发 Page Fault 和 Cache Miss 的不对齐操作也是原子的。

Arm spec

Arm Architecture Reference Manual for A-profile architecture B2.2.1 & B2.5.2

PA问题中,地址对齐的访问是single - copy atomic,非地址对齐的访问在允许 SCTLR_ELx 且芯片支持 LSE,且数据在同一个16b以内是原子的。

内存对齐

  • 结构体中字段的偏移地址和结构体大小与编译器相关;
  • 栈变量和全局变量、静态变量的首地址与编译器相关;
  • 内存申请的首地址与运行时库和操作系统API相关;

gcc & msvc

(23条消息) 结构体字节对齐和位域对齐——VC、gcc_bytxl的博客-CSDN博客_gcc 结构体 起始地址
对齐应该确实与编译器相关,帖子中相当于 packed 属性默认值在 vs 和 gcc 中不一致,但是实际测试应该是都是 8。

linux malloc

malloc(3) - Linux manual page (man7.org)

The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type.

malloc.c - malloc/malloc.c - Glibc source code (glibc-2.37) - Bootlin 行 97

Alignment: 2 * sizeof(size_t) (default) (i.e., 8 byte alignment with 4byte size_t). This suffices for nearly all current machines and C compilers. However, you can define MALLOC_ALIGNMENT to be wider than this if necessary.

linux shmat & mmap

shmat(2): shared memory operations - Linux man page (die.net)

If shmaddr isn't NULL and SHM_RND is specified in shmflg, the attach occurs at the address equal to shmaddr rounded down to the nearest multiple of SHMLBA. Otherwise shmaddr must be a page-aligned address at which the attach occurs

自定义内存分配

自己做内存管理时,比如申请一大片内存,逐个切分时,会导致内存的起始地址不是对齐的,后续的字段也不对齐,从而潜在有并发问题;

PC 问题一些思考

写者持续更新 <a, b, c...>, 产生 <a1, b1, c1...> <a2, b2, c2....>多个版本的数据,读者持续读,预期是读到同一个版本的数据,不产生脏读,即读到 <a_i, b_i, c_i+1, ...>

加锁,如 spinlock

仿写 exanic-software/rwlock.h at master · cisco/exanic-software · GitHub

typedef uint32_t spinlock_t;
void spin_lock(spinlock_t* lock)
{
    uint16_t v2o = __sync_fetch_and_add((violate uint16_t*)lock+1, 1);
    while (*(violate uint16_t*)lock != v2o)
        __ia32_pause();
}

void spin_unlock(spinlock_t* lock)
{
    uint32_t v = *(violate uint32_t*)lock;
    if ((U16)v != (U16)(v >> 16))
        __sync_fetch_and_add((violate uint16_t*)lock, 1);
}

记录每个版本,更新版本号

typedef struct data_t {
    char a;
    char b;
    char c;
    char d;
} data_t

data_t datas[DATA_MAX];
uint32_t dataver;

Writer :                | Reader:
write datas[i]          | read dataver
write_barrier()         | read_barrier()  
write dataver           | write datas[i]

double check

typedef struct data_t {
    char a;
    char b;
    char c;
    char d;
} data_t

data_t data;
uint32_t dataver;

Writer :                | Reader:
write dataver           | 1 read dataver as dataver0
write_barrier()         | 2 read_barrier()  
write data              | 3 write data
                        | 4 if (daraver != dataver0) goto 1

总结

上一篇 下一篇

猜你喜欢

热点阅读