文章

原子操作和原子类型

原子操作不可分割,避免数据竞争;原子类型提供线程安全操作,支持无锁实现,常用于同步与状态共享。

原子操作和原子类型

原子操作和原子类型

原子操作是不可分割的操作,任何线程不会观察到操作“中间态”。如果读取操作是原子的,那么读取到的值要么是初始值,要么是某次完整修改后的值。 非原子操作可能出现“半完成”状态,导致数据竞争和未定义行为。

标准原子类型

  • 标准原子类型定义在 <atomic> 中,操作都是原子的。
  • is_lock_free()成员函数用于查询该原子类型是否使用无锁实现:
    • true 表示无锁,直接用硬件原子指令实现;
    • false 表示内部可能用互斥锁实现。
  • C++17引入 is_always_lock_free 静态常量,编译期表示是否无锁。
  • 相关宏(如 ATOMIC_INT_LOCK_FREE)表示平台上该原子类型是否无锁:
    • 0:有锁实现;
    • 1:运行时判断是否无锁;
    • 2:无锁实现。

std::atomic_flag

  • 唯一保证无锁的原子类型;
  • 只能初始化为“清除”状态,使用宏 ATOMIC_FLAG_INIT 初始化;
  • 提供两个操作:
    • clear(memory_order):清除标志;
    • test_and_set(memory_order):设置标志并返回之前的值。
  • 用于实现自旋锁:
1
2
3
4
5
6
7
8
9
10
11
12
class spinlock_mutex
{
  std::atomic_flag flag;
public:
  spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}
  void lock() {
    while(flag.test_and_set(std::memory_order_acquire));
  }
  void unlock() {
    flag.clear(std::memory_order_release);
  }
};

std::atomic<bool>

  • atomic_flag功能更全;
  • 可以用非原子bool构造和赋值:
1
2
std::atomic<bool> b(true);
b = false;
  • 提供操作:
    • load(memory_order)
    • store(value, memory_order)
    • exchange(new_value, memory_order)
    • compare_exchange_weak(expected, desired, success_order, failure_order)
    • compare_exchange_strong(expected, desired, success_order, failure_order)
  • compare_exchange_weak 可能出现伪失败(spurious failure),通常与循环配合使用:
1
2
3
4
bool expected = false;
while (!b.compare_exchange_weak(expected, true) && !expected) {
  // 循环直到成功或预期值不符
}
  • compare_exchange_strong 无伪失败,直接返回是否成功。

std::atomic<T*>(原子指针)

支持指针的原子操作:

  • load(), store(), exchange(), compare_exchange_weak(), compare_exchange_strong()
  • 指针加减操作:fetch_add(), fetch_sub(),支持 +=, -=, ++, --

示例:

1
2
3
4
5
6
7
8
9
class Foo {};
Foo arr[5];
std::atomic<Foo*> p(arr);
Foo* x = p.fetch_add(2);  // p 指向 arr[2],返回原值 arr
assert(x == arr);
assert(p.load() == &arr[2]);
x = (p -= 1);             // p 指向 arr[1],返回 arr[1]
assert(x == &arr[1]);
assert(p.load() == &arr[1]);

标准原子整型操作

除了 load(), store(), exchange(), compare_exchange_weak()compare_exchange_strong() 外,还支持:

  • fetch_add(), fetch_sub()
  • fetch_and(), fetch_or(), fetch_xor()
  • 复合赋值:+=, -=, &=, |=, ^=
  • 自增自减:++, --

std::atomic<> 类模板(用户自定义类型)

  • 可以使用自定义类型 T 实例化 std::atomic<T>,前提:
    • 类型 T 有编译器生成的拷贝赋值运算符;
    • 无虚函数、无虚基类;
    • 所有成员均支持拷贝赋值。
  • 其实现通常是基于内置类型的字节比较(memcmp),并使用内部锁保护原子操作(大多数平台不支持无锁自定义类型)。
  • 可能的操作只限于 load(), store(), exchange(), compare_exchange_weak(), compare_exchange_strong()
  • 不支持复杂操作或其他运算符重载。
  • 例子:不支持 std::atomic<std::vector<int>>,因为其操作复杂。
  • 浮点类型虽然可以使用,但比较操作可能因不同表示(如NaN等)出现意外行为。

每一个原子类型所能使用的操作:

5-3-table

原子操作的非成员函数

  • C语言兼容的接口,如 std::atomic_load(&a), std::atomic_store(&a, val) 等。
  • 这些非成员函数对应成员函数,增加了兼容性。
  • std::atomic_compare_exchange_weak()std::atomic_compare_exchange_strong() 参数不同,第一个参数是指针,第二个是预期值指针。
  • std::atomic_flag 的非成员函数如 std::atomic_flag_test_and_set() 等也支持显式内存序。

针对智能指针的原子操作

  • C++ 标准库允许对 std::shared_ptr<> 进行原子操作:
    • 不是原子类型,但提供 std::atomic_load(), std::atomic_store(), std::atomic_exchange(), std::atomic_compare_exchange() 等。
  • 这打破了“只有原子类型才有原子操作”的原则,但保证了 shared_ptr 线程安全。

示例:

1
2
3
4
5
6
7
8
9
10
11
std::shared_ptr<my_data> p;

void process_global_data() {
  std::shared_ptr<my_data> local = std::atomic_load(&p);
  process_data(local);
}

void update_global_data() {
  std::shared_ptr<my_data> local(new my_data);
  std::atomic_store(&p, local);
}

小结

  • 原子操作避免了数据竞争和未定义行为。
  • 标准库提供了多种原子类型,满足基本整型、指针、布尔和用户自定义类型的需求。
  • std::atomic_flag 适合实现简单的自旋锁。
  • std::atomic<bool> 提供布尔原子操作,支持比较交换等。
  • std::atomic<T*> 支持指针原子操作和指针算术。
  • 对于用户自定义类型,原子支持有限,通常基于内部锁。
  • 原子操作支持不同内存序,下一节5.3详述内存序的影响。
  • 标准库还提供了与原子操作兼容的非成员函数,支持C语言兼容接口。
  • 智能指针也有对应的原子操作,保证线程安全访问。
本文由作者按照 CC BY 4.0 进行授权