You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

并行编程中READ_ONCE、WRITE_ONCE及ACCESS_ONCE宏的作用与用法咨询

并行编程宏相关问题解答

首先给出你提到的三个宏的定义,方便对照:

#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

#define READ_ONCE(x) \
({ typeof(x) ___x = ACCESS_ONCE(x); ___x; })

#define WRITE_ONCE(x, val) \
do { ACCESS_ONCE(x) = (val); } while (0)

基础宏问题解答

1. ACCESS_ONCE宏的作用是什么,为什么需要将变量转为volatile类型指针后再解引用?

ACCESS_ONCE的核心作用是强制编译器对目标变量执行一次且仅一次内存访问,规避编译器优化带来的非预期行为。
将变量转为volatile指针再解引用,是利用C标准中volatile的语义:要求编译器不得优化掉对volatile内存的访问,也不得合并、重排相邻的多次访问。这种实现不需要修改原变量的类型声明,仅在需要强制单次访问的场景临时生效,不会影响其他代码对该变量的正常优化。

2. READ_ONCE宏末尾的___x有什么作用?

这里用到了GCC的语句表达式语法,该语法规定代码块的返回值为块内最后一个表达式的值。末尾的___x作用就是将刚才用ACCESS_ONCE读到的变量值,作为整个READ_ONCE宏的返回值返回给调用者。
同时先把读到的值存到临时变量___x,还可以保证即使传入的x是复杂表达式,也只会被计算一次,避免重复访问的问题。

场景规则相关问题解答

你提到的5种场景规则如下:

  1. 共享变量仅由指定的所属CPU或线程修改,其他CPU或线程可读。所有存储操作必须使用WRITE_ONCE(),所属CPU或线程可使用普通加载操作,其余所有加载操作必须使用READ_ONCE()。
  2. 共享变量仅在持有指定锁时可修改,未持有该锁的代码可读。所有存储操作必须使用WRITE_ONCE(),持有锁的CPU或线程可使用普通加载操作,其余所有加载操作必须使用READ_ONCE()。
  3. 共享变量仅由指定的所属CPU或线程在持有指定锁时修改,其他CPU或线程、未持有该锁的代码可读。所有存储操作必须使用WRITE_ONCE(),所属CPU或线程、持有锁的任意CPU或线程可使用普通加载操作,其余所有加载操作必须使用READ_ONCE()。
  4. 共享变量仅可由指定CPU或线程、运行在该CPU/线程上下文中的信号或中断处理程序访问。处理程序可使用普通加载存储操作,已阻塞信号/中断、可阻止处理程序被调用的代码也可使用普通加载存储操作,其余所有代码必须使用READ_ONCE()和WRITE_ONCE()。
  5. 共享变量仅可由指定CPU或线程、运行在该CPU/线程上下文中的信号或中断处理程序访问,且处理程序返回前会恢复所有修改过的变量的值。处理程序可使用普通加载存储操作,已阻塞信号/中断、可阻止处理程序被调用的代码也可使用普通加载存储操作,其余所有代码可使用普通加载操作,但必须使用WRITE_ONCE()避免存储撕裂、存储合并和虚构存储问题。

1. volatile关键字无法保证并发内存访问安全,那这些宏是如何实现内存同时访问的?

首先明确:这几个宏本身不提供并发安全性,也不保证CPU层面的内存序,它们解决的只是编译器优化导致的问题,并发安全是由上层的使用规则来保证的。
volatile只能阻止编译器优化掉访问操作、重排编译期的指令顺序,不能阻止CPU层面的指令重排,也不额外提供缓存一致性保证。这些宏的适用场景,都是上层逻辑已经保证了不会出现多写冲突、或者写读冲突导致未定义行为的情况,宏只是保证访问行为符合你的预期:不会出现多次读被优化成单次读、写入被暂存到寄存器不回写内存这类问题。

2. 上述第1个场景中,如何使用READ_ONCE和WRITE_ONCE实现共享变量的无数据竞争访问?

第1个场景的规则本身就已经规避了数据竞争:只有唯一的所属线程/CPU可以修改变量,其他线程仅允许读,不存在多线程同时写的情况。
写端用WRITE_ONCE,是保证对齐的基础类型变量的写入是原子的,不会被编译器拆分成多次写入(避免写撕裂),也不会被优化掉写入操作,保证更新的值立刻写回内存。
读端用READ_ONCE,是保证每次读操作都会真实从内存取值,不会被编译器优化成复用之前存到寄存器的旧值,能拿到写端更新后的最新值,同时也能避免读撕裂。
只要严格遵守“仅所属线程写,其他线程只读”的规则,本身就不存在数据竞争,READ_ONCE/WRITE_ONCE只是保证访问行为符合预期。

3. 上述第2个场景中,写操作仅允许在持有锁时执行,为什么仍要使用WRITE_ONCE宏?为什么读操作不需要持有锁?

关于为什么持有锁写还要用WRITE_ONCE:锁只能保证临界区的互斥执行,但阻止不了编译器对变量访问的优化。如果不用WRITE_ONCE,编译器可能会把临界区内的多次写入合并成一次,或者把写入操作推迟到锁释放之后再执行,这会导致无锁读的线程读到旧值或者非法的中间值。用WRITE_ONCE就是强制写入操作在临界区内按预期执行,立刻写回内存。
关于为什么读不需要持有锁:该场景的规则已经保证了写入操作是原子的(针对对齐的基础类型),不会出现写一半的中间状态,而且写入的逻辑是符合无锁读的预期的(比如只会做状态的单向变更,不会出现非法的中间值),无锁读只要用READ_ONCE就能拿到一个合法的状态值,不需要加锁就能保证读到的是有效数据。这种设计通常用于读多写少的场景,用来规避锁的开销,提升读性能。

内容的提问来源于stack exchange,提问作者aimar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 14:09:03