原子操作和原子类型
原子操作不可分割,避免数据竞争;原子类型提供线程安全操作,支持无锁实现,常用于同步与状态共享。
原子操作和原子类型
原子操作和原子类型
原子操作是不可分割的操作,任何线程不会观察到操作“中间态”。如果读取操作是原子的,那么读取到的值要么是初始值,要么是某次完整修改后的值。 非原子操作可能出现“半完成”状态,导致数据竞争和未定义行为。
标准原子类型
- 标准原子类型定义在
<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等)出现意外行为。
每一个原子类型所能使用的操作:
原子操作的非成员函数
- 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 进行授权