繁体   English   中英

std :: call_once是可重入的还是线程安全的?

[英]Is std::call_once reentrant and thread safe?

std :: call_once是线程安全的,但是它也可以重入吗?

我使用VS2012(Debug和Release)进行的测试表明,从单个线程递归调用std::call_once是可以的,但如果调用是在不同的线程上进行的,则会导致死锁。 这是std::call_once的已知限制吗?

#include "stdafx.h"

#include <iostream>
#include <mutex>
#include <thread>

void Foo()
{
    std::cout << "Foo start" << std::endl;

    std::once_flag flag;
    std::call_once( flag, [](){
        std::cout << "Hello World!" << std::endl;
    });

    std::cout << "Foo end" << std::endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    // Single threaded Works
    {
        std::once_flag fooFlag;
        std::call_once( fooFlag, Foo);      
    }

    // Works
    // Threaded version, join outside call_once
    {
        std::once_flag fooFlag;
        std::thread t;
        std::call_once( fooFlag, [&t](){
            t = std::thread(Foo);
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }); 
        t.join();
    }

    // Dead locks
    // Threaded version, join inside call_once
    {
        std::once_flag fooFlag;
        std::call_once( fooFlag, [](){
            auto t = std::thread(Foo);
            t.join();
        });     
    }

    return 0;
}

看起来像std:call_once正在锁定一个静态互斥锁,在该函数退出之前它不会被解锁。 在单线程情况下它可以工作,因为在第二次调用时线程已经有锁。 在线程版本上,它将阻塞,直到第一个调用退出。

我还注意到,如果将Foo()函数中的std::once_flag标志更改为static ,则仍会发生死锁。

最接近标准的是17.6.5.8 [reentrancy]

1 - 除非在本标准中明确指定,否则它是实现定义的,可以递归地重新输入标准C ++库中的哪些函数。

不幸的是, call_once的规范没有说明它是递归的(或者是跨线程递归的),并且线程支持库前导码也没有说明这个主题。

也就是说,VC ++实现显然不是最理想的,特别是因为可以使用condition_variable编写一个userland版本的call_once

#include <mutex>
#include <condition_variable>

struct once_flag {
  enum { INIT, RUNNING, DONE } state = INIT;
  std::mutex mut;
  std::condition_variable cv;
};
template<typename Callable, typename... Args>
void call_once(once_flag &flag, Callable &&f, Args &&...args)
{
  {
    std::unique_lock<std::mutex> lock(flag.mut);
    while (flag.state == flag.RUNNING) {
      flag.cv.wait(lock);
    }
    if (flag.state == flag.DONE) {
      return;
    }
    flag.state = flag.RUNNING;
  }
  try {
    f(args...);
    {
      std::unique_lock<std::mutex> lock(flag.mut);
      flag.state = flag.DONE;
    }
    flag.cv.notify_all();
  }
  catch (...) {
    {
      std::unique_lock<std::mutex> lock(flag.mut);
      flag.state = flag.INIT;
    }
    flag.cv.notify_one();
    throw;
  }
}

请注意,这是一个细粒度的实现; 也可以编写一个粗粒度的实现,它在所有一次标志中使用一对互斥和条件变量,但是你需要确保在抛出异常时通知所有等待的线程(例如,libc ++这样做) 。

为了提高效率,你可以使once_flag::state成为一个原子并使用双重检查锁定; 为简洁起见,这里省略了。

这里不是“答案”,但至少确认Visual Studio方面的某些内容与您的使用模式有关。

我在coliru.stacked-crooked.com上编译并运行了这个示例,它似乎按预期执行,没有任何死锁。

这是编译行:

g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

这是输出:

Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end

在我指责库实现之前,我个人认为我自己的代码存在问题。

我确实看到Visual Studio的call_once实现有一些类似的问题:

https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&ved=0CDwQFjAC&url=https%3A%2F%2Fconnect.microsoft.com%2FVisualStudio%2Ffeedback%2Fdetails%2F811192%2Fstd-呼叫一旦-挂起&EI = cVw0U-rZBor-2gXCh4HYDw&USG = AFQjCNGDikvgq8YHVzWLC-BqjeOSN-Vm3Q&SIG2 = 7BtB2jH8DeTKEwzAOAf9lw&BVM = bv.63808443,d.b2I及CAD = RJA

http://www.unknownerror.org/Problem/index/1100762158/why-does-this-c-static-singleton-never-stop/

我有兴趣知道Debug或Release中是否出现此问题或两者兼而有之?

毕竟ISO标准确实说call_once是线程安全的!

暂无
暂无

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

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