避免在事务中多次非同步地访问原子对象
16.3 ID_atomicRaces
原子对象可以保证某些特定操作的原子性,但特定操作的组合并不具备原子性,非同步地访问原子对象仍然存在数据竞争。
示例:
atomic_int i = ATOMIC_VAR_INIT(0);
void thd() {
i = i + 1; // Non-compliant, data races
}
设 thd 为线程函数,原子对象 i 在表达式中出现了多次,其读取、计算、写入等过程在多线程中仍然是交织在一起的,造成数据竞争。
应改为:
void thd() {
atomic_fetch_add(&i, 1); // Compliant, or use ‘operator++’ in C++
}
对于一些复杂的原子运算,如:
i = (i + 1) % 5; // Non-compliant
可采用“CAS(compare and swap)” 方法同步:
int old_i = atomic_load(&i);
int new_i = 0;
do {
new_i = (old_i + 1) % 5;
} while (!compare_and_swap(&i, &old_i, new_i)); // Compliant
首先读取原子对象的值 old_i,old_i 经过运算得到新值 new_i,再通过 compare_and_swap 更新原子对象的值。compare_and_swap 具有原子性,将 old_i 和原子对象当前值比较,相等则说明在运算过程中原子对象没有被其他线程更新,将原子对象的值设为 new_i,不相等则说明原子对象已被其他线程更新,将 old_i 设为原子对象当前值,再重复这个过程,直到原子对象可用 new_i 更新。
compare_and_swap 是重要的原子对象同步手段,在实际代码中可与 atomic_compare_exchange_weak、atomic_compare_exchange_strong 等函数对应。