简体   繁体   English

为什么std :: call_once在连接和分离的线程中表现不同?

[英]Why std::call_once behave different in joined and detached threads?

I wrote a small test project to see if std::call_once blocks while executing callable. 我写了一个小测试项目,看看在执行callable时std :: call_once是否阻塞。 Output of the project allows to assume that call_once has 2 behaviours: it blocks on detached threads and does not on joined . 项目的输出允许假设call_once有2个行为:它在分离的线程上阻塞,而在join 阻塞 I strongly suspect that it can not be true, but there is no other conclusion I can make, please guide me to the correct one. 我强烈怀疑这不可能成立,但是我无法得出其他结论,请指导我做出正确的结论。

using namespace std;
once_flag f;
mutex cout_sync;

void my_pause() 
{
  volatile int x = 0;
  for(int i=0; i<2'000'000'000; ++i) { x++; }
}

void thr(int id) 
{
  auto start = chrono::system_clock::now();
  call_once(f, my_pause);
  auto end = chrono::system_clock::now();
  scoped_lock l{cout_sync};
  cout << "Thread " << id << " finished in " << (static_cast<chrono::duration<double>>(end-start)).count() << " sec" << endl;
}

int main() 
{
  vector<thread> threads;
  for(int i=0; i<4; i++)
  {
    threads.emplace_back(thr, i);
    threads.back().join();
  }      
  return 0;
}

Output: 输出:

Thread 0 finished in 4.05423 sec
Thread 1 finished in 0 sec
Thread 2 finished in 0 sec
Thread 3 finished in 0 sec

Changing threads to detached: 将线程更改为分离线程:

for(int i=0; i<4; i++)
{
  threads.emplace_back(thr, i);
  threads.back().detach();
}
this_thread::sleep_for(chrono::seconds(5));

Output: 输出:

Thread 0 finished in 4.08223 sec
Thread 1 finished in 4.08223 sec
Thread 3 finished in 4.08123 sec
Thread 2 finished in 4.08123 sec

Visual Studio 2017 Visual Studio 2017

It is in fact related to the fact that you join the thread first, before starting the next thread, in the joined version. 实际上,这与以下事实有关:您在连接的版本中先加入线程,然后再开始下一个线程。

These semantics are triggered because of the specification of call_once : 这些语义是由于call_once规范而触发

If that invocation throws an exception, it is propagated to the caller of call_once, and the flag is not flipped so that another call will be attempted (such call to call_once is known as exceptional). 如果该调用引发异常,则将其传播到call_once的调用者,并且不会翻转该标志,以便尝试另一个调用(这种对call_once的调用称为异常)。

This means that if the call_once'd function throws an exception, it is not considered to be called, and the next call to call_once will invoke the function again. 这意味着,如果call_once'd函数引发异常,则认为该异常不会被调用,并且对call_once的下一次调用将再次调用该函数。

This means that the entire call_once() is effectively protected by an internal mutex. 这意味着整个call_once()受内部互斥量有效保护。 If a call_once-d function is being executed, any other thread that enters call_once() must be blocked, until the call_once-d function returns. 如果正在执行call_once-d函数,则必须阻止其他任何进入call_once()的线程,直到call_once-d函数返回为止。

You join the threads one at a time, so the 2nd thread doesn't get called until call_once already returned, in the first thread. 您一次加入一个线程,因此直到第一个线程中的call_once返回之前,第二个线程才被调用。

You start all four detached threads effectively at the same time. 您可以同时有效地启动所有四个分离的线程。 Effectively, all four threads will enter call_once approximately together. 实际上,所有四个线程将大致一起输入call_once。

One of those threads will end up executing the called function. 这些线程之一将最终执行被调用的函数。

The other threads will be blocked until the called function either returns, or throws an exception. 其他线程将被阻塞,直到被调用函数返回或引发异常。

This effectively means that all threads will have to wait. 这实际上意味着所有线程都必须等待。

This has nothing to do with detached threads. 这与分离的线程无关。

If you change the first version of the code to start all four threads first, and then join them all, you'll see the same behavior. 如果将代码的第一个版本更改为先启动所有四个线程,然后再将它们全部加入,则将看到相同的行为。

Different isn't the same. 不同是不一样的。

The non-detached threads are being run sequentially — the code waits until one thread finishes before it launches the next. 未分离的线程按顺序运行-代码等待直到一个线程完成,然后再启动下一个线程。 So the first one hits the wait loop and the others don't. 因此,第一个进入等待循环,而其他则没有。

The detached threads run simultaneously. 分离的线程同时运行。 One of them runs the wait loop and the others block until the wait loop is finished. 其中一个运行等待循环,其他运行直到等待循环结束。

Change the code for the non-detached threads to run them simultaneously. 更改非分离线程的代码以同时运行它们。 To do that, move the join outside of the loop that creates the threads. 为此,请将联接移到创建线程的循环之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM