如何在不修改原子对象值的前提下实现memory_order_acq_rel语义?
背景
我有两个无锁链表(属于某内存池,一个存部分空的块,一个存满块)。线程大多遍历第一个链表(寻找分配位置),当某个块变满时,会将其从第一个链表移到第二个链表,正在遍历的线程会继续在第二个链表中遍历。
第一个链表完全通过链表头同步:添加新元素前会完成所有前驱元素的初始化,因此遍历该链表的线程只会访问完全初始化的元素——因为它们加载链表头时,该元素在加入链表前已完成初始化,且其后所有元素也先于它完成初始化(我在链表头部追加元素)。
但有时会直接向第二个链表追加元素(当元素太大无法放入现有块时,会专门分配一个块),此时从第一个链表切换到第二个链表遍历的线程可能会看到未初始化的元素,因为它没有像其他元素那样与第一个链表的头同步。为解决此问题,我希望该元素的加入能纳入元素初始化链,让前驱元素的初始化happen-before它,它happen-before后续元素的初始化。我知道有其他同步方式(比如通过next指针同步),但如前所述,我只想修改直接向第二个链表追加元素的代码。
1. 最优方案:无操作原子读-改-写
对于支持原子读-改-写操作的类型(比如std::atomic<int>),可以直接使用无操作的原子修改操作,指定memory_order_acq_rel语义,无需循环,效率极高:
void create_acq_rel(std::atomic<int>& object) { // fetch_add(0)不修改值,但自带acq_rel语义 object.fetch_add(0, std::memory_order_acq_rel); }
如果是指针类型的原子对象,可以用fetch_or(0, std::memory_order_acq_rel)(对指针做按位或0不会改变值),原理完全一致。
这种方法直接满足需求:
- 原子读-改-写的读阶段自带
acquire语义,确保“读取自”的存储操作happen-before当前操作 - 写阶段自带
release语义,确保当前操作happen-before所有“读取自”它的加载操作 - 全程不修改原子对象的值
2. CAS循环的优化(备选)
如果你的原子类型不支持无操作读-改-写(这种情况极少),可以优化原有的CAS循环:
void create_acq_rel(std::atomic<int>& object) { int value = object.load(std::memory_order_acquire); // compare_exchange_weak在循环中更高效,失败时自动更新value为当前值 while (!object.compare_exchange_weak(value, value, std::memory_order_release, std::memory_order_acquire)) { // 无需手动重新加载value,失败时已自动更新 } }
只要原子对象的修改频率不高,这个循环基本只会执行一次,实际开销远低于预期。
3. 关于内存栅栏的说明
你的理解是正确的:栅栏无法单独建立同步关系,必须配合实际的原子加载/存储操作。比如memory_order_release栅栏需要和后续的原子存储操作配对,且另一个线程的memory_order_acquire加载必须读取到该存储的值,才能建立happen-before关系。因此栅栏无法直接替代原子操作实现你的需求。
内容的提问来源于stack exchange,提问作者Chayim Friedman

