gpt4 book ai didi

c++ - std::call_once 是否可重入且线程安全?

转载 作者:可可西里 更新时间:2023-11-01 18:35:51 30 4
gpt4 key购买 nike

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

我使用 VS2012(调试和发布)进行的测试表明,从单个线程递归调用 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 - Except where explicitly specified in this standard, it is implementation-defined which functions in the Standard C ++ library may be recursively reentered.

不幸的是,call_once 的规范没有说明它是否递归(或跨线程递归),线程支持库序言也没有说明这个主题。

也就是说,VC++ 实现显然不是最优的,特别是因为可以使用 condition_variable 编写 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 成为原子并使用双重检查锁定;为简洁起见,此处省略。

关于c++ - std::call_once 是否可重入且线程安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22692783/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com