Based on How to implement timeout for function in c++, I wrote this wrapper:
template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
// if 't_function' return type is 'void'
std::is_void_v<std::invoke_result_t<t_function, const bool &, t_params...>>,
// the 'execute' wrapper will return 'bool', which will be 'true' if the
// 'p_function' executes in less 'p_max_time', or 'false' otherwise
bool,
// else it will result a 'std::optional' with the return type of
// 't_function', which will contain a value of that type, if the
// 'p_function' executes in less 'p_max_time', or empty otherwise
std::optional<std::invoke_result_t<t_function, const bool &, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
std::mutex _mutex;
std::condition_variable _cond;
bool _timeout{false};
typedef typename std::invoke_result_t<t_function, const bool &, t_params...>
t_ret;
if constexpr (std::is_void_v<t_ret>) {
std::thread _th([&]() -> void {
p_function(_timeout, std::forward<t_params>(p_params)...);
_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_mutex};
if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return true;
}
_timeout = true;
_th.detach();
return false;
} else {
t_ret _ret;
std::thread _th([&]() -> void {
_ret = p_function(_timeout, std::forward<t_params>(p_params)...);
_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_mutex};
if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return {std::move(_ret)};
}
_timeout = true;
_th.detach();
return {};
}
}
Unlike the code in the answers for the question I referenced, I would not like to throw an exception in the execute wrapper. If p_function does not return, the wrapper will return a bool, true if p_function executed in at most p_max_time, false otherwise. If p_function returns T, the wrapper will return std::optional<T> which will have a value if p_function does not exceed p_max_time, or it will be empty otherwise.
The const bool & parameter required for p_function is used to inform p_function that its execution exceeded p_max_time, so p_function may stop its execution, though execute will not count on it.
Here is an example:
auto _function = [](const bool &p_is_timeout, int &&p_i) -> void {
std::this_thread::sleep_for(1s);
if (p_is_timeout) {
std::cout << "timeout";
} else {
std::cout << "i = " << p_i << '\n';
}
};
int _i{4};
if (!async::execute(200ms, _function, std::move(_i))) {
std::cout << "TIMEOUT!!\n";
}
So, the problem is _th.detach() causes crash when I execute some test functions in a row. I if change it to _th.join(), the crash no longer occurs, but, obviously, the function that calls the wrapper has to wait for p_function to end, which is not desired.
How can I make execute detach _th thread, without causing crash?
Your lambda needs access to the local variables _ret and cond these don't exist after the end of execute so your code has undefined behaviour. Lambdas that capture by reference should only be used when the lambda has the same lifetime as the code that they're defined in.
You'd need to define your state variables in the heap so that they exist after the end of the function. For example you could use a shared_ptr:
template <typename Result>
struct state
{
std::mutex _mutex;
std::condition_variable _cond;
Result _ret;
};
template <>
struct state<void>
{
std::mutex _mutex;
std::condition_variable _cond;
};
template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
// if 't_function' return type is 'void'
std::is_void_v<std::invoke_result_t<t_function, const bool &, t_params...>>,
// the 'execute' wrapper will return 'bool', which will be 'true' if the
// 'p_function' executes in less 'p_max_time', or 'false' otherwise
bool,
// else it will result a 'std::optional' with the return type of
// 't_function', which will contain a value of that type, if the
// 'p_function' executes in less 'p_max_time', or empty otherwise
std::optional<std::invoke_result_t<t_function, const bool &, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
typedef typename std::invoke_result_t<t_function, const bool &, t_params...>
t_ret;
auto _state = std::make_shared<state<t_ret>>();
bool _timeout{false};
if constexpr (std::is_void_v<t_ret>) {
std::thread _th([&, _state]() -> void {
p_function(_timeout, std::forward<t_params>(p_params)...);
_state->_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_state->_mutex};
if (_state->_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return true;
}
_timeout = true;
_th.detach();
return false;
} else {
std::thread _th([&, _state]() -> void {
_state->_ret = p_function(_timeout, std::forward<t_params>(p_params)...);
_state->_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_state->_mutex};
if (_state->_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return {std::move(_state->_ret)};
}
_timeout = true;
_th.detach();
return {};
}
}
Note for simplicity I've left in the capture of the function arguments and function by reference but you still need to ensure those references remain valid for as long as they're needed (e.g. if the timeout is short the function could exit before the target function executes or if the arguments are references then the invoked function can't use those references after the function exits).
If you have c++20 you might want to look into std::jthread
Based on answers and suggestions, I came up with:
template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
// if 't_function' return type is 'void'
std::is_void_v<
std::invoke_result_t<t_function, std::function<bool()>, t_params...>>,
// the 'execute' wrapper will return 'bool', which will be 'true' if the
// 'p_function' executes in less 'p_max_time', or 'false' otherwise
bool,
// else it will result a 'std::optional' with the return type of
// 't_function', which will contain a value of that type, if the
// 'p_function' executes in less 'p_max_time', or empty otherwise
std::optional<
std::invoke_result_t<t_function, std::function<bool()>, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
std::mutex _mutex;
std::condition_variable _cond;
auto _timeout = std::make_shared<bool>(false);
auto _is_timeout = [_timeout]() { return *_timeout; };
typedef typename std::invoke_result_t<t_function, std::function<bool()>,
t_params...>
t_ret;
if constexpr (std::is_void_v<t_ret>) {
std::thread _th([&]() -> void {
p_function(_is_timeout, std::forward<t_params>(p_params)...);
_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_mutex};
if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return true;
}
*_timeout = true;
_th.join();
return false;
} else {
t_ret _ret;
std::thread _th([&]() -> void {
_ret = p_function(_is_timeout, std::forward<t_params>(p_params)...);
_cond.notify_one();
});
std::unique_lock<std::mutex> _lock{_mutex};
if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
_th.join();
return {std::move(_ret)};
}
*_timeout = true;
_th.join();
return {};
}
}
And the example becomes:
auto _function = [](std::function<bool()> p_timeout, int &&p_i) -> void {
std::this_thread::sleep_for(1s);
if (p_timeout()) {
std::cout << "timeout in work function\n";
} else {
std::cout << "i = " << p_i << '\n';
}
};
int _i{4};
if (!execute(200ms, _function, std::move(_i))) {
std::cout << "OK - timeout\n";
}
else {
std::cout << "NOT OK - no timeout\n";
}
I believe passing a std::function<bool()> to the work function (p_function) creates a good abstraction on how execute will control the timeout, and an easy way for p_function to check for it.
I also removed the std::thread::detach() calls.
Related
I am trying to chained coroutines. Foo2 will actually go async. Once Foo2 resume, the code should execute in the order of "resume Foo2" and "resume Foo1" (like 2 continuation). I am not clear on some details. First, when co_await b suspends, does it return a promise object immediately to the caller? Then co_await Foo2() happens. At this point I need to suspend but don't want to fire off thread t(run). Somewhere I think I need to wrap the promise/awaiter from Foo2() before co_await on it in Foo1().
void run(std::coroutine_handle<> h)
{
std::cout<<std::this_thread::get_id()<<" "<<"in Run\n";
std::this_thread::sleep_for (std::chrono::seconds(5));
h.resume();
}
template<typename T>
struct task{
struct promise_type {
T val_;
task get_return_object() { return {.h_ = std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(T val) { val_ = val; }
void unhandled_exception() {}
};
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h)
{
std::thread t(run, h);
t.detach();
}
void await_resume() { }
std::coroutine_handle<promise_type> h_;
};
template<typename T>
task<T> Foo2()
{
std::cout<<std::this_thread::get_id()<<" "<<"in Foo2\n";
task<T> b;
co_await b;
std::cout<<std::this_thread::get_id()<<" resume Foo2\n";
}
template<typename T>
task<T> Foo1()
{
std::cout<<std::this_thread::get_id()<<" "<<"in Foo1\n";
co_await Foo2<T>();
std::cout<<std::this_thread::get_id()<<" resume Foo1\n";
}
int main()
{
Foo1<int>();
std::cout<<std::this_thread::get_id()<<" ""main end\n";
std::this_thread::sleep_for (std::chrono::seconds(30));
}
does it return a promise object immediately
Promise objects are never returned. Promise object is created and stored inside the coroutine frame. It is destroyed together with coroutine frame.
What is returned to caller is "return object" of coroutine. It is created from object that is returned from promise::get_return_object() function when the coroutine first suspends (or is done). (promise::get_return_object() is called before the body of the coroutine starts its execution)
At this point I need to suspend...
In order to await completion of another coroutine current coroutine needs to be suspended and stored somewhere to be resumed after awaited coroutine completes.
It can be stored outside in some context that is responsible for spinning coroutines (something like io_service) or inside awaited coroutine.
Here is an example of asynchronous (and single-threaded) task<T> coroutine that is awaitable.
It stores suspended coroutine inside coroutine promise of awaited coroutine and resumes it after coroutine value is computed.
#include <coroutine>
#include <optional>
#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <vector>
// basic coroutine single-threaded async task example
template<typename T>
struct task_promise_type;
// simple single-threaded timer for coroutines
void submit_timer_task(std::coroutine_handle<> handle, std::chrono::seconds timeout);
template<typename T>
struct task;
template<typename T>
struct task_promise_type
{
// value to be computed
// when task is not completed (coroutine didn't co_return anything yet) value is empty
std::optional<T> value;
// corouine that awaiting this coroutine value
// we need to store it in order to resume it later when value of this coroutine will be computed
std::coroutine_handle<> awaiting_coroutine;
// task is async result of our coroutine
// it is created before execution of the coroutine body
// it can be either co_awaited inside another coroutine
// or used via special interface for extracting values (is_ready and get)
task<T> get_return_object();
// there are two kinds of coroutines:
// 1. eager - that start its execution immediately
// 2. lazy - that start its execution only after 'co_await'ing on them
// here I used eager coroutine task
// eager: do not suspend before running coroutine body
std::suspend_never initial_suspend()
{
return {};
}
// store value to be returned to awaiting coroutine or accessed through 'get' function
void return_value(T val)
{
value = std::move(val);
}
void unhandled_exception()
{
// alternatively we can store current exeption in std::exception_ptr to rethrow it later
std::terminate();
}
// when final suspend is executed 'value' is already set
// we need to suspend this coroutine in order to use value in other coroutine or through 'get' function
// otherwise promise object would be destroyed (together with stored value) and one couldn't access task result
// value
auto final_suspend() noexcept
{
// if there is a coroutine that is awaiting on this coroutine resume it
struct transfer_awaitable
{
std::coroutine_handle<> awaiting_coroutine;
// always stop at final suspend
bool await_ready() noexcept
{
return false;
}
std::coroutine_handle<> await_suspend(std::coroutine_handle<task_promise_type> h) noexcept
{
// resume awaiting coroutine or if there is no coroutine to resume return special coroutine that do
// nothing
return awaiting_coroutine ? awaiting_coroutine : std::noop_coroutine();
}
void await_resume() noexcept {}
};
return transfer_awaitable{awaiting_coroutine};
}
// there are multiple ways to add co_await into coroutines
// I used `await_transform`
// use `co_await std::chrono::seconds{n}` to wait specified amount of time
auto await_transform(std::chrono::seconds duration)
{
struct timer_awaitable
{
std::chrono::seconds duration;
// always suspend
bool await_ready()
{
return false;
}
// h is a handler for current coroutine which is suspended
void await_suspend(std::coroutine_handle<task_promise_type> h)
{
// submit suspended coroutine to be resumed after timeout
submit_timer_task(h, duration);
}
void await_resume() {}
};
return timer_awaitable{duration};
}
// also we can await other task<T>
template<typename U>
auto await_transform(task<U>& task)
{
if (!task.handle) {
throw std::runtime_error("coroutine without promise awaited");
}
if (task.handle.promise().awaiting_coroutine) {
throw std::runtime_error("coroutine already awaited");
}
struct task_awaitable
{
std::coroutine_handle<task_promise_type<U>> handle;
// check if this task already has value computed
bool await_ready()
{
return handle.promise().value.has_value();
}
// h - is a handle to coroutine that calls co_await
// store coroutine handle to be resumed after computing task value
void await_suspend(std::coroutine_handle<> h)
{
handle.promise().awaiting_coroutine = h;
}
// when ready return value to a consumer
auto await_resume()
{
return std::move(*(handle.promise().value));
}
};
return task_awaitable{task.handle};
}
};
template<typename T>
struct task
{
// declare promise type
using promise_type = task_promise_type<T>;
task(std::coroutine_handle<promise_type> handle) : handle(handle) {}
task(task&& other) : handle(std::exchange(other.handle, nullptr)) {}
task& operator=(task&& other)
{
if (handle) {
handle.destroy();
}
handle = std::exchange(other.handle, nullptr);
}
~task()
{
if (handle) {
handle.destroy();
}
}
// interface for extracting value without awaiting on it
bool is_ready() const
{
if (handle) {
return handle.promise().value.has_value();
}
return false;
}
T get()
{
if (handle) {
return std::move(*handle.promise().value);
}
throw std::runtime_error("get from task without promise");
}
std::coroutine_handle<promise_type> handle;
};
template<typename T>
task<T> task_promise_type<T>::get_return_object()
{
return {std::coroutine_handle<task_promise_type>::from_promise(*this)};
}
// simple timers
// stored timer tasks
struct timer_task
{
std::chrono::steady_clock::time_point target_time;
std::coroutine_handle<> handle;
};
// comparator
struct timer_task_before_cmp
{
bool operator()(const timer_task& left, const timer_task& right) const
{
return left.target_time > right.target_time;
}
};
std::priority_queue<timer_task, std::vector<timer_task>, timer_task_before_cmp> timers;
void submit_timer_task(std::coroutine_handle<> handle, std::chrono::seconds timeout)
{
timers.push(timer_task{std::chrono::steady_clock::now() + timeout, handle});
}
// timer loop
void loop()
{
while (!timers.empty()) {
auto& timer = timers.top();
// if it is time to run a coroutine
if (timer.target_time < std::chrono::steady_clock::now()) {
auto handle = timer.handle;
timers.pop();
handle.resume();
} else {
std::this_thread::sleep_until(timer.target_time);
}
}
}
// example
using namespace std::chrono_literals;
task<int> wait_n(int n)
{
std::cout << "before wait " << n << '\n';
co_await std::chrono::seconds(n);
std::cout << "after wait " << n << '\n';
co_return n;
}
task<int> test()
{
for (auto c : "hello world\n") {
std::cout << c;
co_await 1s;
}
std::cout << "test step 1\n";
auto w3 = wait_n(3);
std::cout << "test step 2\n";
auto w2 = wait_n(2);
std::cout << "test step 3\n";
auto w1 = wait_n(1);
std::cout << "test step 4\n";
auto r = co_await w2 + co_await w3;
std::cout << "awaiting already computed coroutine\n";
co_return co_await w1 + r;
}
// main can't be a coroutine and usually need some sort of looper (io_service or timer loop in this example )
int main()
{
// do something
auto result = test();
// execute deferred coroutines
loop();
std::cout << "result: " << result.get();
}
Output:
hello world
test step 1
before wait 3
test step 2
before wait 2
test step 3
before wait 1
test step 4
after wait 1
after wait 2
after wait 3
awaiting already computed coroutine
result: 6
I'm sorry if I'm getting the whole concept wrong, but I'm trying to make a tuple the container of the actual objects, where only with its destruction those objects will go out of scope.
I currently have this:
class MiniThread {
public:
~MiniThread() {
if (m_thread) {
if (m_thread->joinable())
m_thread->join();
delete m_thread;
}
}
void join()
{
if (m_thread == nullptr)
return;
m_thread->join();
m_thread = nullptr;
}
template<typename F, typename... Args>
void run(F func, Args... args)
{
if (m_thread != nullptr)
join();
auto tuple = std::forward_as_tuple(args...);
m_thread = new std::thread([=]() {
__try
{
std::apply(func, tuple);
}
__except (CrashDump::GenerateDump(GetExceptionInformation()))
{
// TODO: log.
exit(1);
}
});
m_started = true;
}
bool started() const { return m_started; }
private:
std::thread *m_thread = nullptr;
bool m_started = false;
};
std::string getString()
{
return std::string("sono");
}
int main()
{
auto test = [&](std::string seila, const std::string& po, std::promise<int>* p)
{
std::cout << seila.c_str() << std::endl;
std::cout << po.c_str() << std::endl;
p->set_value(10);
};
std::promise<int> p;
std::future<int> f;
MiniThread thread;
std::string hello = "hello";
std::string seilapo = "seilapo";
f = p.get_future();
thread.run(test, getString(), "how are you", &p);
thread.join();
int ftest = f.get();
std::cout << ftest << std::endl;
}
By the time the thread is ran, the args are no longer reliable. They have been destructed already. So I was wondering if is there a way to copy them in the call of the thread by value. I have made some attempts of moving the variadic arguments into tuples, but tuples are always rendered with rvalues and fail all the same.
This:
auto tuple = std::forward_as_tuple(args...);
Creates a tuple of references into args... That's what forward_as_tuple's job is. You're then capturing that tuple of references by value:
m_thread = new std::thread([=]{ /* ... */ });
So once your arguments go out of scope, you're only holding onto references to them... and that'll dangle.
But you don't actually... need to have a tuple at all. Just copy the arguments themselves:
m_thread = std::thread([=]() {
func(args...); // func and args, no tuple here
});
Also don't write new thread - thread is already a handle type, just create one.
The above copies the arguments. If you want to move them, then in C++17, yes you'll need to have a tuple and use std::apply. But not forward_as_tuple... just make_tuple:
m_thread = std::thread([func, args=std::make_tuple(std::move(args)...)]() mutable {
std::apply(func, std::move(args));
});
In C++20, you won't need the tuple again, and can write a pack-expansion:
m_thread = std::thread([func, ...args=std::move(args)]() mutable {
func(std::move(args)...);
});
I have the following MpscQueue implementation
EDIT: added an is_running atomic, but problem still persists.
template<typename T>
class MpscQueue {
public:
MpscQueue() = default;
MpscQueue(MpscQueue&&) = delete;
bool wait_and_pop(T& val, std::atomic<bool>& is_running) {
std::unique_lock<std::mutex> lock(mutex);
cond_var.wait(lock,
[this, &is_running]{ return queue.size() > 0 || !is_running; });
if (!is_running) return false;
val = std::move(queue.front());
queue.pop();
return true;
}
template<typename U>
void push(U&& val) {
auto const is_empty = [&]{
auto const lock = std::unique_lock(mutex);
auto const res = queue.empty();
queue.push(std::forward<U>(val));
return res;
}();
if (is_empty) cond_var.notify_one();
}
private:
std::queue<T> queue;
std::mutex mutex;
std::condition_variable cond_var;
};
I am attempting to pop a value like this
// At some point earlier
MpscQueue<Message> mailbox;
std::atomic<boo> is_running{true}; // Is set to false at a later time
void run_once() {
Message m;
mailbox.wait_and_pop(m, is_running);
// process_message(std::move(m));
}
The above code run_once is being fed into the thread constructor. My issue is that if I attempt to join the thread that this is on, it gets stuck in the condition variable wait condition. What would be the best way to solve this? I tried passing an atomic by reference as a parameter into wait_and_pop but it did not seem to be updating and also did not seem like a smart implementation decision.
I have the following thread pool implementation:
template<typename... event_args>
class thread_pool{
public:
using handler_type = std::function<void(event_args...)>;
thread_pool(handler_type&& handler, std::size_t N = 4, bool finish_before_exit = true) : _handler(std::forward<handler_type&&>(handler)),_workers(N),_running(true),_finish_work_before_exit(finish_before_exit)
{
for(auto&& worker: _workers)
{
//worker function
worker = std::thread([this]()
{
while (_running)
{
//wait for work
std::unique_lock<std::mutex> _lk{_wait_mutex};
_cv.wait(_lk, [this]{
return !_events.empty() || !_running;
});
//_lk unlocked
//check to see why we woke up
if (!_events.empty()) {//was it new work
std::unique_lock<std::mutex> _readlk(_queue_mutex);
auto data = _events.front();
_events.pop();
_readlk.unlock();
invoke(std::move(_handler), std::move(data));
_cv.notify_all();
}else if(!_running){//was it a signal to exit
break;
}
//or was it spurious and we should just ignore it
}
});
//end worker function
}
}
~thread_pool()
{
if(_finish_work_before_exit)
{//block destruction until all work is done
std::condition_variable _work_remains;
std::mutex _wr;
std::unique_lock<std::mutex> lk{_wr};
_work_remains.wait(lk,[this](){
return _events.empty();
});
}
_running=false;
//let all workers know to exit
_cv.notify_all();
//attempt to join all workers
for(auto&& _worker: _workers)
{
if(_worker.joinable())
{
_worker.join();
}
}
}
handler_type& handler()
{
return _handler;
}
void propagate(event_args&&... args)
{
//lock before push
std::unique_lock<std::mutex> _lk(_queue_mutex);
{
_events.emplace(std::make_tuple(args...));
}
_lk.unlock();//explicit unlock
_cv.notify_one();//let worker know that data is available
}
private:
bool _finish_work_before_exit;
handler_type _handler;
std::queue<std::tuple<event_args...>> _events;
std::vector<std::thread> _workers;
std::atomic_bool _running;
std::condition_variable _cv;
std::mutex _wait_mutex;
std::mutex _queue_mutex;
//helpers used to unpack tuple into function call
template<typename Func, typename Tuple, std::size_t... I>
auto invoke_(Func&& func, Tuple&& t, std::index_sequence<I...>)
{
return func(std::get<I>(std::forward<Tuple&&>(t))...);
}
template<typename Func, typename Tuple, typename Indicies = std::make_index_sequence<std::tuple_size<Tuple>::value>>
auto invoke(Func&& func, Tuple&& t)
{
return invoke_(std::forward<Func&&>(func), std::forward<Tuple&&>(t), Indicies());
}
};
I recently added this section to the destructor:
if(_finish_work_before_exit)
{//block destruction until all work is done
std::condition_variable _work_remains;
std::mutex _wr;
std::unique_lock<std::mutex> lk{_wr};
_work_remains.wait(lk,[this](){
return _events.empty();
});
}
The intent was to have the destructor block until the work queue was fully consumed.
But it seems to put the program into deadlock. aAll of the work does get completed, but the wait does not seem to end when the work is done.
Consider this example main:
std::mutex writemtx;
thread_pool<int> pool{
[&](int i){
std::unique_lock<std::mutex> lk{writemtx};
std::cout<<i<<" : "<<std::this_thread::get_id()<<std::endl;
},
8//threads
};
for (int i=0; i<8192; ++i) {
pool.propagate(std::move(i));
}
How can I have the destructor wait for the completion of the work without causing deadlock?
The reason your code is deadlocked is that _work_remains is a condition variable which is not "notified" by any part of your code. You would need to make that a class attribute and have it notified by any thread that picks up the last event from the _events.
I am implementing a concurrent wrapper as introduced by Herb Sutter presented in his talk "C++ and Beyond 2012".
template <typename T>
class ConcurrentWrapper {
private:
std::deque<std::unique_ptr<std::function<void()>>> _tasks;
std::mutex _mutex;
std::condition_variable _cond;
T _object;
std::thread _worker;
std::atomic<bool> _done {false};
public:
template <typename... ArgsT>
ConcurrentWrapper(ArgsT&&... args) :
_object {std::forward<ArgsT>(args)...},
_worker {
[&]() {
typename decltype(_tasks)::value_type task;
while(!_done) {
{
std::unique_lock<std::mutex> lock(_mutex);
while(_tasks.empty()) {
_cond.wait(lock);
}
task = std::move(_tasks.front());
_tasks.pop_front();
}
(*task)();
}
}
} {
}
~ConcurrentWrapper() {
{
std::unique_lock<std::mutex> lock(_mutex);
_tasks.push_back(std::make_unique<std::function<void()>>(
[&](){_done = true;}
));
}
_cond.notify_one();
_worker.join();
}
template <typename F, typename R = std::result_of_t<F(T&)>>
std::future<R> operator()(F&& f) {
std::packaged_task<R(T&)> task(std::forward<F>(f));
auto fu = task.get_future();
{
std::unique_lock<std::mutex> lock(_mutex);
_tasks.push_back(std::make_unique<std::function<void()>>(
[this, task=MoveOnCopy<decltype(task)>(std::move(task))]() {
task.object(this->_object);
}
));
}
_cond.notify_one();
return fu;
}
};
Basically, the idea is to wrap an object and provide thread-safe access in FIFO order using operation (). However, in some runs (not always happen), the following program hanged:
ConcurrentWrapper<std::vector<int>> results;
results(
[&](std::vector<T>& data) {
std::cout << "sorting...\n";
std::sort(data.begin(), data.end());
std::cout << "done ...\n";
EXPECT_EQ(data, golden);
}
).get();
However, the program work correctly without explicitly calling get() method.
results(
[&](std::vector<T>& data) {
std::cout << "sorting...\n";
std::sort(data.begin(), data.end());
std::cout << "done ...\n";
EXPECT_EQ(data, golden);
}
); // Function correctly without calling get
What could the be problem? Did I implement something wrong? I noticed a posted here saying that "a packaged_task needs to be invoked before you call f.get(), otherwise you program will freeze as the future will never become ready." Is this true? If yes, how can I get this problem solved?
I was compiling the code using -std=c++1z -pthread with G++ 6.1