5.2.3 std::atomic<bool>的相关操作
最基本的内置类型就是std::atomic<bool>,这是个比atomic_flag更为全面的bool量。尽管它仍然不支持拷贝构造和拷贝赋值,但是它可以从一个非原子类型的bool量构造,也可以用一个非原子类型来给std::atomic<bool>赋值:
std::atomic<bool> b(true);
b=false;
需要注意的是,复制操作并不像我们平时那样返回对象引用,而是返回一个bool值。这是另一个原子类型的特点:为了避免其他代码为了从一个引用获取其值之前被其他线程改变。通过返回一个非原子类型的值,能够确保此刻得到的值就是当前原子对象中存储的实际值。
相比于atomic_flog,test_and_set()函数被替换成exchange(),设置新的值,返回旧的值。想获取其值既可以使用load(),也可以隐式的转换。同样,想设置它的值,既可以使用store()函数也可以使用隐式转换。
atomic<bool>不仅支持exchange()这样的read-modify-write类型的操作,还支持“比较/交换”操作。
根据当前值决定是否存储新的值
这种操作叫做“比较/交换”,有两个这样的成员函数:compare_exchange_weak()、compare_exchange_strong()。
compare_exchange_weak()的一个函数声明是:
bool atomic<bool>::compare_exchange_weak( bool& expected_value, bool new_value, memory_order _Order) noexcept;
参数expected_value代表在执行这个函数之前的一个期待值,或者说预测值。
函数首先比较atomic<bool>中的值是否与expected_value想等,如果想等则将new_value的值保存到atomic<bool>中,返回true;如果不相等,则将atomic<bool>当前的值保存到expected_value中,返回false。
对于compare_exchange_weak()函数来说,即使expected_value的值与atomic<bool>的当前值想等,仍然有可能交换失败并返回false。这最可能发生在缺乏单个比较/交换指令的机器上,如果处理器不能保证比较/交换操作被原子化,那么当线程数大于处理器核数时,可能发生一个线程在操作一半的时候被切出,此时另一个线程切入并继续做了某些操作。这被叫做虚假失败(spurious failure)。
根据www.cplusplus.com的描述:On these spurious failures, the function returns false
while not modifying expected.
如果出现虚假失败,这个函数返回false,并且没有修改expected参数。
因为compare_exchange_weak()可能出现虚假失败,因此必须使用循环调用的方式:
bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);
如果是真的失败,那么compare_exchange_weak()的返回值应该是false、并且expected的值应该是true,那么循环就终止了。除非compare_exchange_weak()的返回值是false,但expected的值依然是false,这明显代表是假失败,因此循环将继续。
另一个函数compare_exchange_strong()则不同,它保证只有std::atomic<bool>对象的值不等于expected的值时才返回false。这样就没必要使用循环了。因为当他返回false时,说明操作的确是失败的。其实在某些机器,某些算法上,compare_exchange_strong()函数内部包含了循环,其性能会比compare_exchange_weak()差一些。
如果你无论如何都想更新当前原子变量的值(或许想更新为一个依赖当前值的新值),那么就需要每次更新expected的值。如果此时没有其他线程在修改原子变量,那么compare_exchange_weak()和compare_exchange_strong()两个函数都能保证在循环时执行成功。如果获取expected值的耗时很小,那么循环使用compare_exchange_weak()是个好的选择;如果获取expected值的耗时很大,那么如果expected的值没有改变的话,使用compare_exchange_strong()更好,因为它不需要重新获取expected的值。对于 std::atomic<bool>来说,这可能无关紧要,但是对于其他原子类型,这可能是个不得不面对的问题。
比较/交换操作可以接受两个内存指令参数,它们用指定执行成功或失败时使用的内存指令。比较完美的调用是,成功时使用memory_order_acq_rel指令,失败时使用memory_order_relaxed指令。一次失败的操作并没有对原子对象执行保存操作,因此不能使用memory_order_release或memory_order_acq_rel指令。你也不能为执行失败提供比执行成功更为严格的内存指令,例如,如果你想给执行失败时指定memory_order_acquire或memory_order_seq_cst指令,那么你必须给执行成功时指定同样的指令。
std::atomic<bool>与std::atomic_flag的一个不同之处是,std::atomic<bool>可能不是无锁的。为了保证原子操作,内部实现可能使用了mutex。可以使用is_lock_free()来检测。除了std::atomic_flag外,其他的标准原子类型都具有这一特点。