由于包含在 std::atomic 对象中的变量预计可以从多个线程访问,因此人们应该期望它们的行为至少与使用 volatile 关键字声明的一样。
这是在 CPU 架构引入高速缓存行等之前的标准和推荐做法。
[EDIT2] 有人可能会说 std::atomic 是多核时代的volatile 变量。正如在 C/C++ 中定义的那样,volatile 仅足以同步来自单个线程的原子读取,同时 ISR 修改变量(在这种情况下,它实际上是原子写入,从主线程)。
我个人感到欣慰的是,没有编译器会优化对原子变量的写入。如果写入被优化掉,你如何保证这些写入中的每一个都可能被其他线程中的读者看到?不要忘记这也是 std::atomic 合约的一部分。
考虑这段代码,编译器的疯狂优化会极大地影响结果。
#include <atomic>
#include <thread>
static const int N{ 1000000 };
std::atomic<int> flag{1};
std::atomic<bool> do_run { true };
void write_1()
{
while (do_run.load())
{
flag = 1; flag = 1; flag = 1; flag = 1;
flag = 1; flag = 1; flag = 1; flag = 1;
flag = 1; flag = 1; flag = 1; flag = 1;
flag = 1; flag = 1; flag = 1; flag = 1;
}
}
void write_0()
{
while (do_run.load())
{
flag = -1; flag = -1; flag = -1; flag = -1;
}
}
int main(int argc, char** argv)
{
int counter{};
std::thread t0(&write_0);
std::thread t1(&write_1);
for (int i = 0; i < N; ++i)
{
counter += flag;
std::this_thread::yield();
}
do_run = false;
t0.join();
t1.join();
return counter;
}
[编辑] 起初,我并没有提出 volatile 是原子实现的核心,但是...
由于似乎有人怀疑volatile是否与原子有关,所以我调查了此事。这是来自 VS2017 stl 的原子实现。据我推测,volatile 关键字无处不在。
// from file atomic, line 264...
// TEMPLATE CLASS _Atomic_impl
template<unsigned _Bytes>
struct _Atomic_impl
{ // struct for managing locks around operations on atomic types
typedef _Uint1_t _My_int; // "1 byte" means "no alignment required"
constexpr _Atomic_impl() _NOEXCEPT
: _My_flag(0)
{ // default constructor
}
bool _Is_lock_free() const volatile
{ // operations that use locks are not lock-free
return (false);
}
void _Store(void *_Tgt, const void *_Src, memory_order _Order) volatile
{ // lock and store
_Atomic_copy(&_My_flag, _Bytes, _Tgt, _Src, _Order);
}
void _Load(void *_Tgt, const void *_Src,
memory_order _Order) const volatile
{ // lock and load
_Atomic_copy(&_My_flag, _Bytes, _Tgt, _Src, _Order);
}
void _Exchange(void *_Left, void *_Right, memory_order _Order) volatile
{ // lock and exchange
_Atomic_exchange(&_My_flag, _Bytes, _Left, _Right, _Order);
}
bool _Compare_exchange_weak(
void *_Tgt, void *_Exp, const void *_Value,
memory_order _Order1, memory_order _Order2) volatile
{ // lock and compare/exchange
return (_Atomic_compare_exchange_weak(
&_My_flag, _Bytes, _Tgt, _Exp, _Value, _Order1, _Order2));
}
bool _Compare_exchange_strong(
void *_Tgt, void *_Exp, const void *_Value,
memory_order _Order1, memory_order _Order2) volatile
{ // lock and compare/exchange
return (_Atomic_compare_exchange_strong(
&_My_flag, _Bytes, _Tgt, _Exp, _Value, _Order1, _Order2));
}
private:
mutable _Atomic_flag_t _My_flag;
};
MS stl 中的所有专业化都在关键功能上使用 volatile。
这是其中一个关键函数的声明:
inline int _Atomic_compare_exchange_strong_8(volatile _Uint8_t *_Tgt, _Uint8_t *_Exp, _Uint8_t _Value, memory_order _Order1, memory_order _Order2)
您会注意到所需的volatile uint8_t* 包含包含在std::atomic 中的值。这种模式可以在整个 MS std::atomic 实现中观察到,这不是 gcc 团队或任何其他 stl 提供者采取不同做法的理由。