Kan*_* Li 9 c++ multithreading c++11
将std::call_once正常的非原子变量?请考虑以下代码
std::once_flag once;
int x;
void init() { x = 10; }
void f() {
std::call_once(once, init);
assert(x == 10);
}
int main() {
std::thread t1(f), t2(f);
t1.join();
t2.join();
}
Run Code Online (Sandbox Code Playgroud)
返回时是否init会在所有线程中看到副作用call_once?有关cppreference的文档有点模糊.它只表示所有线程std::call_once将在init完成后返回,但没有提到阻止x = 10在init返回后重新排序的任何内容.
有任何想法吗?澄清行为的标准在哪里?
当call_once返回时,是否会在所有线程中看到init中的副作用?
init所有已调用的线程都可以看到副作用.call_once
只有一个活动执行(调用init),但可以执行多个被动执行.
§30.4.6.2-2 - [thread.once.callonce]
不调用其func的call_once的执行是被动执行.调用其func的call_once的执行是一个活动执行.
§30.4.6.2-3 - [thread.once.callonce]
同步:对于任何给定的once_flag:所有活动执行都按总顺序发生; 完成一个有效执行与(6.8.2)该总订单中下一个的开始同步; 并且返回的执行与所有被动执行的返回同步.
所以它完全符合您的预期
原子变量和非原子变量之间的主要区别在于,从多个线程访问非原子变量(除非所有线程都在读取)需要显式同步,以防止访问可能并发。
有多种方法可以实现这种同步。最常见的技术涉及互斥体。一个线程对互斥体的解锁与另一线程对该互斥体的后续锁定同步。因此,如果第一个线程写入变量而第二个线程读取该变量,则写入和读取之间存在显式顺序。然后程序将按照您的预期运行:读取必须看到按该顺序写入的最后一个值。如果不使用互斥体,则对变量的访问可能会并发,并且会发生未定义的行为。
原子变量是自同步的:无论如何,尝试访问同一原子变量的两个线程都会在它们之间计算出某种顺序。除此之外,与非原子变量相比,它们没有任何特殊的能力可以被多个线程访问。
多个线程使用std::call_once具有相同标志的 会建立显式同步:每个线程仅从完成std::call_once一次返回,因此每个线程必须看到 的新值。initx
仅允许编译器在不改变程序的可观察行为的范围内对写入进行重新排序。一旦您遵守标准,不允许对非原子变量的写入与对同一变量的另一次访问可能并发,您在重新排序方面合理化的竞争条件就会消失。