5.2 C++中的原子操作和原子类型
原子操作 是个不可分割的操作。
在系统的所有线程中,你是不可能观察到原子操作完成了一半这种情况的;
它要么就是做了,要么就是没做,只有这两种可能。
如果从对象读取值的加载操作是 原子 的,而且对这个对象的所有修改操作也是 原子 的,
那么加载操作得到的值要么是对象的初始值,要么是某次修改操作存入的值。
另一方面,非原子操作可能会被另一个线程观察到只完成一半。
如果这个操作是一个存储操作,那么其他线程看到的值,可能既不是存储前的值,也不是存储的值,而是别的什么值。
如果这个非原子操作是一个加载操作,它可能先取到对象的一部分,然后值被另一个线程修改,然后它再取到剩余的部分,
所以它取到的既不是第一个值,也不是第二个值,而是两个值的某种组合。
正如第三章所讲的,这一下成了一个容易出问题的竞争冒险,
但在这个层面上它可能就构成了 数据竞争 (见5.1节),就成了未定义行为。
在C++中,多数时候你需要一个原子类型来得到原子的操作,我们来看一下这些类型。
5.2.1 标准原子类型
标准 原子类型 定义在头文件中。
这些类型上的所有操作都是原子的,在语言定义中只有这些类型的操作是原子的,不过你可以用互斥锁来 模拟 原子操作。
实际上,标准原子类型自己的实现就可能是这样模拟出来的:
它们(几乎)都有一个is_lock_free()成员函数,
这个函数让用户可以查询某原子类型的操作是直接用的原子指令(x.is_lock_free()返回true),
还是编译器和库内部用了一个锁(x.is_lock_free()返回false)。
只用std::atomic_flag类型不提供is_lock_free()成员函数。这个类型是一个简单的布尔标志,并且在这种类型上的操作都需要是无锁的;当你有一个简单无锁的布尔标志时,你可以使用其实现一个简单的锁,并且实现其他基础的原子类型。当你觉得“真的很简单”时,就说明:在std::atomic_flag对象明确初始化后,做查询和设置(使用test_and_set()成员函数),或清除(使用clear()成员函数)都很容易。这就是:无赋值,无拷贝,没有测试和清除,没有其他任何操作。
剩下的原子类型都可以通过特化std::atomic<>类型模板而访问到,并且拥有更多的功能,但可能不都是无锁的(如之前解释的那样)。在最流行的平台上,期望原子变量都是无锁的内置类型(例如std::atomic和std::atomic),但这没有必要。你在后面将会看到,每个特化接口所反映出的类型特点;位操作(如&=)就没有为普通指针所定义,所以它也就不能为原子指针所定义。
除了直接使用std::atomic<>类型模板外,你可以使用在表5.1中所示的原子类型集。由于历史原因,原子类型已经添加入C++标准中,这些备选类型名可能参考相应的std::atomic<>特化类型,或是特化的基类。在同一程序中混合使用备选名与std::atomic<>特化类名,会使代码的移植大打折扣。
表5.1 标准原子类型的备选名和与其相关的std::atomic<>特化类
原子类型
相关特化类
atomic_bool
std::atomic
atomic_char
std::atomic
atomic_schar
std::atomic
atomic_uchar
std::atomic
atomic_int
std::atomic
atomic_uint
std::atomic
atomic_short
std::atomic
atomic_ushort
std::atomic
atomic_long
std::atomic
atomic_ulong
std::atomic
atomic_llong
std::atomic
atomic_ullong
std::atomic
atomic_char16_t
std::atomic
atomic_char32_t
std::atomic
atomic_wchar_t
std::atomic
C++标准库不仅提供基本原子类型,还定义了与原子类型对应的非原子类型,就如同标准库中的std::size_t。如表5.2所示这些类型:
表5.2 标准原子类型定义(typedefs)和对应的内置类型定义(typedefs)
原子类型定义
标准库中相关类型定义
atomic_int_least8_t
int_least8_t
atomic_uint_least8_t
uint_least8_t
atomic_int_least16_t
int_least16_t
atomic_uint_least16_t
uint_least16_t
atomic_int_least32_t
int_least32_t
atomic_uint_least32_t
uint_least32_t
atomic_int_least64_t
int_least64_t
atomic_uint_least64_t
uint_least64_t
atomic_int_fast8_t
int_fast8_t
atomic_uint_fast8_t
uint_fast8_t
atomic_int_fast16_t
int_fast16_t
atomic_uint_fast16_t
uint_fast16_t
atomic_int_fast32_t
int_fast32_t
atomic_uint_fast32_t
uint_fast32_t
atomic_int_fast64_t
int_fast64_t
atomic_uint_fast64_t
uint_fast64_t
atomic_intptr_t
intptr_t
atomic_uintptr_t
uintptr_t
atomic_size_t
size_t
atomic_ptrdiff_t
ptrdiff_t