无其他线程执行release操作时,加锁是否无需添加acquire屏障?
当无其他线程执行解锁操作时,加锁是否可以省略acquire语义?
本问题是此前关于「单次自旋锁是否需要内存acquire屏障」问题的延伸。
常规锁的acquire/release语义示例
正常情况下,加锁时需要添加acquire语义,解锁时添加release语义以避免竞争,示例如下:
code block0
atomic_int lock = 0; // 多线程执行 void threads(void) { while (atomic_exchange_explicit(&lock, 1, memory_order_acquire)); // 加锁(acquire语义) // 临界区操作 atomic_store_explicit(&lock, 0, memory_order_release); // 解锁(release语义) }
无竞争场景下的relaxed加锁示例
在无竞争的场景下(比如仅单个线程执行相关代码),加锁时无需添加acquire语义,此处用try-lock指代该逻辑:
code block1
atomic_int lock = 0; // 多线程执行 void threads(void) { int v = 0; // try_lock,此处可用relaxed语义 if (!atomic_compare_exchange_strong_explicit(&lock, &v, 1, memory_order_relaxed, memory_order_relaxed)) { return; // try_lock失败则返回 } // 临界区操作 // 永不解锁 }
核心问题:无外部release时,加锁是否需要acquire?
现在探讨核心问题:当没有其他线程执行解锁(release)操作时,加锁是否可以不添加acquire屏障?
抽象示例(code block2)
#define EXIT_FLAG 1 #define WORK_FLAG 2 atomic_int state = 0; void thread0(void) { int tmp; while (1) { tmp = 0; // 此处是否需要acquire语义? if (!atomic_compare_exchange_strong_explicit(&state, &tmp, WORK_FLAG, memory_order_relaxed, memory_order_relaxed)) { assert(tmp == EXIT_FLAG); return; } // 执行工作任务 tmp = WORK_FLAG; // 此处必须用release语义,配合thread1的acquire if (!atomic_compare_exchange_strong_explicit(&state, &tmp, 0, memory_order_release, memory_order_relaxed)) { assert(tmp == (EXIT_FLAG | WORK_FLAG)); // 执行清理操作 return; } } } void thread1(void) { int tmp = 0; while (1) { // 此处必须用acquire语义,配合thread0的release if (atomic_compare_exchange_strong_explicit(&state, &tmp, tmp | EXIT_FLAG, memory_order_acquire, memory_order_relaxed)) break; } if (!(tmp & WORK_FLAG)) { // 执行清理操作 } }
我们需要避免工作任务(do work)与清理操作(do clean)产生竞争,以下是更具体的实现示例:
具体实现示例(code block3)
#define EXIT_FLAG 1 #define WORK_FLAG 2 atomic_int state = 0; struct work_struct *work_data; // 用calloc初始化 void thread0(void) { int tmp; while (1) { tmp = 0; // 此处是否需要acquire语义? if (!atomic_compare_exchange_strong_explicit(&state, &tmp, WORK_FLAG, memory_order_relaxed, memory_order_relaxed)) { assert(tmp == EXIT_FLAG); return; } read/write(*work_data); // 读写工作数据 tmp = WORK_FLAG; // 此处必须用release语义,配合thread1的acquire if (!atomic_compare_exchange_strong_explicit(&state, &tmp, 0, memory_order_release, memory_order_relaxed)) { assert(tmp == (EXIT_FLAG | WORK_FLAG)); free(work_data); work_data = NULL; return; } } } void thread1(void) { int tmp = 0; while (1) { // 此处必须用acquire语义,配合thread0的release if (atomic_compare_exchange_strong_explicit(&state, &tmp, tmp | EXIT_FLAG, memory_order_acquire, memory_order_relaxed)) break; } if (!(tmp & WORK_FLAG)) { free(work_data); work_data = NULL; } }
逻辑抽象(code block4)
lock_t lock; // 工作线程 void thread0(void) { while (1) { if (!try_lock_relaxed(&lock)) // 此处是否需要acquire语义?我认为不需要 return; // 执行工作任务 unlock_release(&lock); // 必须用release语义 } } // 其他线程 void threads(void) { // 此处必须用acquire语义,配合thread0的unlock_release if (!try_lock_acquire(&lock)) return; // 执行清理或其他工作 // 永不解锁 }
结论:不需要添加acquire语义
在thread0的场景下,完全不需要在加锁时添加acquire语义,理由如下:
- 线程内天然有序:thread0的加锁操作是承接自己上一次的解锁(release)操作,同一线程内的指令执行天然有顺序一致性,不需要acquire来约束内存可见性。
- acquire的核心作用不适用:acquire语义的核心是确保加锁后的操作能看到其他线程解锁前的所有修改,但此处thread0是唯一执行解锁操作的线程,不存在“其他线程的修改”需要同步的场景。
- 已有release语义保障:thread0上一次解锁的release语义已经确保了之前的工作任务操作完成并对后续可见,因此本次加锁后的工作任务不需要额外的acquire来保障内存可见性。
内容的提问来源于stack exchange,提问作者untitled
相关产品推荐
相关产品推荐

