To be specific, boost::future
doesn't seem to block in the destructor. And boost documentation didn't mention it. However std::future
did mention it.
If I want to chain some .then()
to boost::future
I also need to chain a .get()
call at the end to force the temporary object to block to get the correct behavior. Is that the right way of using it?
void a()
{
boost::async([] {
boost::this_thread::sleep_for(boost::chrono::seconds{1});
std::cout << "Finished sleeping\n";
return 1;
});
std::cout << "End of a()\n";
}
void b()
{
std::async([] {
std::this_thread::sleep_for(std::chrono::seconds{1});
std::cout << "Finished sleeping\n";
return 1;
});
std::cout << "End of b()\n";
}
int main()
{
a();//No finished sleeping printed
std::cout << "End of main()\n";
}
int main()
{
b();//finished sleeping will print
std::cout << "End of main()\n";
}
https://www.boost.org/doc/libs/1_75_0/doc/html/thread/synchronization.html
The returned futures behave as the ones returned from boost::async, the destructor of the future object returned from then will block. This could be subject to change in future versions.
(2015) https://www.boost.org/doc/libs/1_75_0/doc/html/thread/changes.html
Version 4.5.0 - boost 1.58
Fixed Bugs:
#10968 The futures returned by async() and future::then() are not blocking.
(2017) https://github.com/boostorg/thread/issues/194
I'm aware of the issue. Boost.thread did it by design following std::async and the first proposals of std::future::then.
The problem is that changing the behavior (even if it is not desired in some contexts) would be a breaking change. I would need to crate a new version, but Idon't want to create a new version using compiler flags as this has become a nightmare to maintain.
So I will need to create a boost/thread_v5 which will produce different binaries lib_boost_thread_v5.a. Boost.Threads is not structured this way, and this will mean a lot of work.
This is the single way to avoid breaking changes, but before doing that I would like to enumerate all the breaking changes that should be on this new version.
It seems the destructor of future object returned from async
has really changed to non-blocking at some time point after 2017, so you should use .get()
to block.
I can not make it block because:
BOOST_THREAD_FUTURE_BLOCKING
(not documented) is not #define
d in all cases;#define BOOST_THREAD_FUTURE_BLOCKING
manually, you can see that ~future_async_shared_state_base()
will be called when the future
is destroyed and the lambda passed to async has finished running (i.e. when the boost::shared_ptr
's use_count drops from 2 to 1 to 0), so the destructor of future object returned from async
(when the boost::shared_ptr
's use_count drops from 2 to 1) will still not block.https://www.boost.org/doc/libs/1_75_0/boost/thread/detail/config.hpp
#if BOOST_THREAD_VERSION>=5
//#define BOOST_THREAD_FUTURE_BLOCKING
#if ! defined BOOST_THREAD_PROVIDES_EXECUTORS \
&& ! defined BOOST_THREAD_DONT_PROVIDE_EXECUTORS
#define BOOST_THREAD_PROVIDES_EXECUTORS
#endif
#else
//#define BOOST_THREAD_FUTURE_BLOCKING
#define BOOST_THREAD_ASYNC_FUTURE_WAITS
#endif
https://www.boost.org/doc/libs/1_75_0/boost/thread/future.hpp
in async
:
void init(BOOST_THREAD_FWD_REF(Fp) f)
{
#ifdef BOOST_THREAD_FUTURE_BLOCKING
this->thr_ = boost::thread(&future_async_shared_state::run, static_shared_from_this(this), boost::forward<Fp>(f));
#else
boost::thread(&future_async_shared_state::run, static_shared_from_this(this), boost::forward<Fp>(f)).detach();
#endif
}
when the future
is destroyed and the lambda passed to async has finished running:
~future_async_shared_state_base()
{
#ifdef BOOST_THREAD_FUTURE_BLOCKING
join();
#elif defined BOOST_THREAD_ASYNC_FUTURE_WAITS
unique_lock<boost::mutex> lk(this->mutex);
this->waiters.wait(lk, boost::bind(&shared_state_base::is_done, boost::ref(*this)));
#endif
}
C++20 has moved to coroutine which is able to split control flow in more complex cases than future.then: CppCon 2015: Gor Nishanov “C++ Coroutines - a negative overhead abstraction". You can try libraries like cppcoro or boost.fiber and pause the task function inside complex control structures (e.g. co_await
inside if
inside while
inside for
).
C++23 Executor TS: in initial versions of A Unified Executors Proposal for C++ there were OneWayExecutor
execute
(fire-and-forget) TwoWayExecutor
twoway_execute
(return a future) then_execute
(take a future and a function, return a future) etc but in recent versions they were replaced by sender and receiver.