[英]Non-obvious lifetime issue with std::promise and std::future
這個問題與前一個問題非常類似: pthread_once()中的競爭條件?
它本質上是同一個問題 - 在調用promise::set_value
期間結束的std::promise
的生命周期(即:在標記相關聯的未來之后,但在pthread_once
執行之前)
所以我知道我的用法有這個問題,因此我不能以這種方式使用它。 但是,我認為這是不明顯的。 (用Scott Meyer明智的話說: 使接口易於正確使用且難以正確使用 )
我在下面提供一個范例:
dispatcher
),它在一個隊列上旋轉,彈出一個'job'(一個std::function
)並執行它。 synchronous_job
的實用程序類,它阻止調用線程,直到在調度程序線程上執行'job' std::promise
和std::future
是synchronous_job
成員 - 一旦設置了future
,阻塞的調用線程就會繼續,這會導致synchronous_job
彈出堆棧並被破壞。 dispatcher
在context promise::set_value
內部進行了上下文切換; future
被標記,但是對pthread_once
的調用沒有執行,並且pthread堆棧以某種方式被破壞,這意味着下一次: 死鎖 我希望對promise::set_value
的調用是原子的; 它在標記future
之后需要做更多的工作這一事實在以這種方式使用這些類時將不可避免地導致這種問題。
所以我的問題是:如何使用std::promise
和std::future
實現這種同步,保持它們的生命周期與提供這種同步機制的類相關聯?
@Jonathan Wakely,你是否可以在內部使用一些RAII風格的類,它在標記future
之后在其析構函數中設置condition_variable
? 這意味着即使在調用set_value
的過程中破壞了promise
,設置條件變量的額外工作也會正確完成。 只是一個想法,不確定你是否可以使用它...
下面是一個完整的工作示例,以及之后的死鎖應用程序的堆棧跟蹤:
#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();
}
}
死鎖app的堆棧跟蹤:
線程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
對象。
但是,由於這個未來用於一次性同步,所以無論如何都不需要這樣做:在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
有一個共享狀態,它們都指向並保證在雙方都被破壞之前保持可用狀態。 從概念上講,這類似於承諾和未來都具有shared_ptr的單獨副本到同一個對象。 此對象包含傳遞狀態,塊和其他操作所必需的基礎機制。
至於試圖發出破壞信號,問題是這個條件變量存在於何處? 一旦所有相關的期貨和承諾被銷毀,共享區域就會被銷毀。 發生死鎖是因為該區域在被使用時被破壞(因為編譯器不知道另一個線程仍在訪問承諾,因為它正在被銷毀)。 將其他條件變量添加到任何共享狀態都無濟於事,因為它們也會被破壞。
回答我自己的問題,提供可行的解決方案。 它不使用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 ::綁定到此而是std :: weak_ptr。 當你得到回調時,在調用回調之前鎖定()並檢查NULL。
或者,重新聲明,永遠不要從不包含shared_ptr的作用域調用成員函數(從外部)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.