std :: call_once对非原子变量安全吗?

时间:2018-01-12 07:27:45

标签: 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();
}

init返回时,是否会在call_once中看到std::call_once中的副作用? cppreference上的文档有点模糊。它只表示所有线程init将在init完成后返回,但没有提及阻止在class NavigationBar: UINavigationBar { var hideBackItem = true private let emptyTitle = "" override func layoutSubviews() { if let `topItem` = topItem, topItem.backBarButtonItem?.title != emptyTitle, hideBackItem { topItem.backBarButtonItem = UIBarButtonItem(title: emptyTitle, style: .plain, target: nil, action: nil) } super.layoutSubviews() } } 返回后重新排序x = 10的任何内容。

有什么想法吗?澄清行为的标准在哪里?

2 个答案:

答案 0 :(得分:8)

  

当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)该总订单中下一个的开始同步;   并且返回的执行与所有被动执行的返回同步。

所以它完全符合您的预期

答案 1 :(得分:4)

原子变量和非原子变量之间的主要区别在于,从多个线程访问非原子变量(除非所有线程都在读取)需要显式同步以防止访问可能并发。

有多种方法可以实现此同步。最常见的技术涉及互斥体。一个线程解锁互斥锁与另一个线程随后锁定该互斥锁同步。因此,如果第一个线程写入变量而第二个线程读取该变量,则在写入和读取之间存在显式排序。然后程序按预期运行:读取必须看到以该顺序写入的最后一个值。如果未使用互斥锁,则对变量的访问可能会并发,并且会发生未定义的行为。

原子变量是自同步的:无论如何,试图访问相同原子变量的两个线程将在它们之间产生一些顺序。除此之外,与非原子变量相比,它们没有任何特殊能力可由多个线程访问。

多个线程使用具有相同标志的std::call_once设置显式同步:每个线程仅在std::call_once完成后从init返回,所以每个线程必须看到x的新值。

只允许编译器对写入进行重新排序,使其不会改变程序的可观察行为。一旦遵守标准,您在重新排序方面合理化的竞争条件就会消失,因为不允许对非原子变量的写入可能与对同一变量的另一次访问同时发生。