std::promise和std::future的寿命问题不明显
Non-obvious lifetime issue with std::promise and std::future
这个问题与前面的问题非常相似:pthread_once()中的竞赛条件?
这本质上是同一个问题——std::promise
的生存期在调用promise::set_value
期间结束(即:在相关的未来被标记之后,但在pthread_once
执行之前)
所以我知道我的用法有这个问题,因此我不能以这种方式使用它。然而,我认为这并不明显。(用Scott Meyer的名言:使接口易于正确使用,难以错误使用)
我举一个例子如下:
- 我有一个线程(
dispatcher
),它在队列上旋转,弹出一个"作业"(std::function
)并执行它 - 我有一个名为
synchronous_job
的实用程序类,它会阻塞调用线程,直到在调度器线程上执行了"作业"> std::promise
和std::future
是synchronous_job
的成员-一旦设置了future
,阻塞的调用线程就会继续,这导致synchronous_job
从堆栈中弹出并被销毁- 不幸的是,此时
dispatcher
被上下文切换,而在promise::set_value
内部;future
被标记,但对pthread_once
的调用尚未执行,pthread堆栈在某种程度上已损坏,这意味着下一次将出现:死锁
我希望对promise::set_value
的调用是原子调用;当以这种方式使用这些类时,它在标记CCD_ 17之后需要做更多的工作这一事实将不可避免地导致这种问题。
因此,我的问题是:如何使用std::promise
和std::future
实现这种同步,使它们的寿命与提供这种同步机制的类相关联?
@Jonathan Wakely,您是否可以在内部使用某种RAII风格的类,在标记future
之后,在其析构函数中设置condition_variable
?这意味着,即使promise
在调用set_value
的过程中被破坏,设置条件变量的额外工作也将正确完成。只是一个想法,不确定你是否可以使用它…
下面是一个完整的工作示例,以及之后死锁应用程序的堆栈跟踪:
#include <iostream>
#include <thread>
#include <future>
#include <queue>
struct dispatcher
{
dispatcher()
{
_thread = std::move(std::thread(&dispatcher::loop, this));
}
void post(std::function<void()> job)
{
std::unique_lock<std::mutex> l(_mtx);
_jobs.push(job);
_cnd.notify_one();
}
private:
void loop()
{
for (;;)
{
std::function<void()> job;
{
std::unique_lock<std::mutex> l(_mtx);
while (_jobs.empty())
_cnd.wait(l);
job.swap(_jobs.front());
_jobs.pop();
}
job();
}
}
std::thread _thread;
std::mutex _mtx;
std::condition_variable _cnd;
std::queue<std::function<void()>> _jobs;
};
//-------------------------------------------------------------
struct synchronous_job
{
synchronous_job(std::function<void()> job, dispatcher& d)
: _job(job)
, _d(d)
, _f(_p.get_future())
{
}
void run()
{
_d.post(std::bind(&synchronous_job::cb, this));
_f.wait();
}
private:
void cb()
{
_job();
_p.set_value();
}
std::function<void()> _job;
dispatcher& _d;
std::promise<void> _p;
std::future<void> _f;
};
//-------------------------------------------------------------
struct test
{
test()
: _count(0)
{
}
void run()
{
synchronous_job job(std::bind(&test::cb, this), _d);
job.run();
}
private:
void cb()
{
std::cout << ++_count << std::endl;
}
int _count;
dispatcher _d;
};
//-------------------------------------------------------------
int main()
{
test t;
for (;;)
{
t.run();
}
}
死锁应用程序的堆栈跟踪:
线程1(主线程)
#0 0x00007fa112ed750c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007fa112a308ec in __gthread_cond_wait (__mutex=<optimized out>, __cond=<optimized out>) at /hostname/tmp/syddev/Build/gcc-4.6.2/gcc-build/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:846
#2 std::condition_variable::wait (this=<optimized out>, __lock=...) at ../../../../libstdc++-v3/src/condition_variable.cc:56
#3 0x00000000004291d9 in std::condition_variable::wait<std::__future_base::_State_base::wait()::{lambda()#1}>(std::unique_lock<std::mutex>&, std::__future_base::_State_base::wait()::{lambda()#1}) (this=0x78e050, __lock=..., __p=...) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/condition_variable:93
#4 0x00000000004281a8 in std::__future_base::_State_base::wait (this=0x78e018) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/future:331
#5 0x000000000042a2d6 in std::__basic_future<void>::wait (this=0x7fff0ae515c0) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/future:576
#6 0x0000000000428dd8 in synchronous_job::run (this=0x7fff0ae51580) at /home/lorimer/p4/Main/Source/Trading/Confucius/Test/Scratch/Test1/main.cpp:60
#7 0x0000000000428f97 in test::run (this=0x7fff0ae51660) at /home/lorimer/p4/Main/Source/Trading/Confucius/Test/Scratch/Test1/main.cpp:83
#8 0x0000000000427ad6 in main () at /home/lorimer/p4/Main/Source/Trading/Confucius/Test/Scratch/Test1/main.cpp:99
线程2(调度器线程)
#0 0x00007fa112ed8b5b in pthread_once () from /lib64/libpthread.so.0
#1 0x0000000000427946 in __gthread_once (__once=0x78e084, __func=0x4272d0 <__once_proxy@plt>) at /hostname/sdk/gcc470/suse11/x86_64/bin/../lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/x86_64-unknown-linux-gnu/bits/gthr-default.h:718
#2 0x000000000042948b in std::call_once<void (std::__future_base::_State_base::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>&, bool&), std::__future_base::_State_base* const, std::reference_wrapper<std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()> >, std::reference_wrapper<bool> >(std::once_flag&, void (std::__future_base::_State_base::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>&, bool&), std::__future_base::_State_base* const&&, std::reference_wrapper<std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()> >&&, std::reference_wrapper<bool>&&) (__once=..., __f=
@0x7fa111ff6be0: (void (std::__future_base::_State_base::*)(std::__future_base::_State_base * const, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>()> &, bool &)) 0x42848a <std::__future_base::_State_base::_M_do_set(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>&, bool&)>) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/mutex:819
#3 0x000000000042827d in std::__future_base::_State_base::_M_set_result(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>, bool) (this=0x78e018, __res=..., __ignore_failure=false) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/future:362
#4 0x00000000004288d5 in std::promise<void>::set_value (this=0x7fff0ae515a8) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/future:1206
#5 0x0000000000428e2a in synchronous_job::cb (this=0x7fff0ae51580) at /home/lorimer/p4/Main/Source/Trading/Confucius/Test/Scratch/Test1/main.cpp:66
#6 0x000000000042df53 in std::_Mem_fn<void (synchronous_job::*)()>::operator() (this=0x78c6e0, __object=0x7fff0ae51580) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/functional:554
#7 0x000000000042d77c in std::_Bind<std::_Mem_fn<void (synchronous_job::*)()> (synchronous_job*)>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) (this=0x78c6e0, __args=...) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/functional:1156
#8 0x000000000042cb28 in std::_Bind<std::_Mem_fn<void (synchronous_job::*)()> (synchronous_job*)>::operator()<, void>() (this=0x78c6e0) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/functional:1215
#9 0x000000000042b772 in std::_Function_handler<void (), std::_Bind<std::_Mem_fn<void (synchronous_job::*)()> (synchronous_job*)> >::_M_invoke(std::_Any_data const&) (__functor=...) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/functional:1926
#10 0x0000000000429f2c in std::function<void ()>::operator()() const (this=0x7fa111ff6da0) at /hostname/sdk/gcc470/suse11/x86_64/include/c++/4.7.0/functional:2311
#11 0x0000000000428c3c in dispatcher::loop (this=0x7fff0ae51668) at /home/lorimer/p4/Main/Source/Trading/Confucius/Test/Scratch/Test1/main.cpp:39
std::promise
和其他对象一样:一次只能从一个线程访问它。在这种情况下,您正在调用set_value()
,并在没有足够同步的情况下从单独的线程销毁对象:规范中没有任何地方表明set_value
在准备好future
后不会接触promise
对象。
然而,由于这个future用于一次性同步,所以无论如何都不需要这样做:在run()
中创建promise/future对,并将promise传递给线程:
struct synchronous_job
{
synchronous_job(std::function<void()> job, dispatcher& d)
: _job(job)
, _d(d)
{
}
void run(){
std::promise<void> p;
std::future<void> f=p.get_future();
_d.post(
[&]{
cb(std::move(p));
});
f.wait();
}
private:
void cb(std::promise<void> p)
{
_job();
p.set_value();
}
std::function<void()> _job;
dispatcher& _d;
};
在直接回答您的问题时,正确的答案是将std::promise
提供给线程。这样,只要线程需要,它就保证存在
在引擎盖下,std::future
和std::promise
具有一个共享状态,两者都指向该状态,并保证在双方都被摧毁之前保持可用。从概念上讲,这类似于promise和future都具有到同一对象的shared_ptr的单独副本。此对象包含传递状态、块和其他操作所必需的底层机制。
至于试图发出破坏信号,问题是这个条件变量会存在于哪里?一旦所有相关的未来和承诺都被摧毁,共享区域就会被摧毁。死锁的发生是因为区域在被使用时正在被销毁(因为编译器不知道另一个线程在销毁promise时仍在访问它)。向任何共享状态添加额外的条件变量都没有帮助,因为它们也会被销毁。
回答我自己的问题,提供一个可行的解决方案。它不使用std::promise
或std::future
,但它实现了我正在搜索的同步。
更新synchronous_job
以使用std::condition_variable
和std::mutex
:
编辑:更新为包含Dave S建议的布尔标志
struct synchronous_job
{
synchronous_job(std::function<void()> job, dispatcher& d)
: _job(job)
, _d(d)
, _done(false)
{
}
void run()
{
_d.post(std::bind(&synchronous_job::cb, this));
std::unique_lock<std::mutex> l(_mtx);
if (!_done)
_cnd.wait(l);
}
private:
void cb()
{
_job();
std::unique_lock<std::mutex> l(_mtx);
_done = true;
_cnd.notify_all();
}
std::function<void()> _job;
dispatcher& _d;
std::condition_variable _cnd;
std::mutex _mtx;
bool _done;
};
标准答案是永远不要std::绑定到this,而是绑定到std::weak_ptr。当您获得回调时,请锁定()并在调用回调之前检查NULL。
或者,重申,永远不要从不包含对象shared_ptr的作用域调用成员函数(从外部)。
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- 为什么 std::unique 不调用 std::sort?
- 为什么std::valarray不是算术的
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- 带有指定长度字符* 参数的 std::regex_search 在 VS2017 中不起作用?
- 为什么在C的循环中使用printf的Rust代码不显示输出,而在C++的循环中显示std::cout
- 为什么std::isnan 不是 constexpr?
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 命名空间 std:: 不包含可选
- std 不包含,但它应该添加库
- 返回值std ::不匹配的相等向量
- 尽管Sfinae,这个程序还是不明显的
- 是否有两个不平等大小的向量的STD ::不匹配
- std::promise和std::future的寿命问题不明显
- “std”不会在函数之前命名类型和预期的初始值设定项
- std::不带模板的数组迭代器范围
- 错误 C2871:'std'不存在具有此名称的命名空间
- 在Windows Kinect应用程序中使用c++和c#有什么不明显的区别吗?(例如,性能、特性)
- std::ifstream::read()读取的数据少于请求的数据,并设置失败位,原因不明显