Using co_await vs std async for asynchronous wait - c++

I have seen examples where a thread is created while doing co_await in a coroutine, e.g. from https://en.cppreference.com/w/cpp/language/coroutines#co_await
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out) {
struct awaitable {
std::jthread* p_out;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::jthread& out = *p_out;
if (out.joinable())
throw std::runtime_error("Output jthread parameter not empty");
out = std::jthread([h] { h.resume(); });
// Potential undefined behavior: accessing potentially destroyed *this
// std::cout << "New thread ID: " << p_out->get_id() << '\n';
std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
}
void await_resume() {}
};
return awaitable{&out};
}
struct task{
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
task resuming_on_new_thread(std::jthread& out) {
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main() {
std::jthread out;
resuming_on_new_thread(out);
}
The application flow from a caller's point of view is very similar to blocking on std async when the function is executed in a different thread. Can someone advice in what situation or use case would calling asynchronous function using co_await on a thread be better than using std::async with std:launch::async?

Related

c++: condition variable ownership

I am facing an issue while performing thread synchronisation.
I have a class very similar to the ThreadQueue implementation proposed in this answer, which I'll briefly report here for completeness:
#include <mutex>
#include <queue>
#include <condition_variable>
template <typename T>
class ThreadQueue {
std::queue<T> q_;
std::mutex mtx;
std::condition_variable cv;
public:
void enqueue (const T& t) {
{
std::lock_guard<std::mutex> lck(mtx);
q_.push(t);
}
cv.notify_one();
}
T dequeue () {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [this] { return !q_.empty(); });
T t = q_.front();
q_.pop();
return t;
}
};
I have a consumer that continuously extracts the first available item of a shared instance of that class, say ThreadQueue<int> my_queue;, until it receives a signal to quit, for instance:
std::atomic_bool quit(false);
void worker(){
std::cout << "[worker] starting..." << std::endl;
while(!quit.load()) {
std::cout << "[worker] extract element from the queue" << std::endl;
auto el = my_queue.dequeue();
std::cout << "[worker] consume extracted element" << std::endl;
std::cout << el << std::endl;
}
std::cout << "[worker] exiting" << std::endl;
}
Suppose the program has to terminate (for any reason) before any producer can insert elements in the queue; in this case the worker would be stuck on the line auto el = my_queue.dequeue(); and cannot terminate.
An exemple of this case is the following:
int main() {
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "[main] terminating..." << std::endl;
quit.store(true);
t.join();
std::cout << "[main] terminated!" << std::endl;
return 0;
}
Clearly, the worker can be "unlocked" by pushing a dummy element in the queue, but it does not seem an elegant solution.
I am thus wondering whether the thread syncronisation on the empty queue should be taken out of the ThreadQueue class and done inside the worker instead, i.e. moving the "ownership" of the condition variable outside the ThreadQueue container.
In general, is a class such as ThreadQueue always a bad design?
In case it's not, is there any solution that allows to keep the condition variable encapsulated in ThreadQueue, hence removing the responsibility of thread syncronisation from the users of that class (bearing in mind I am limited to usage of C++11)?
Full MWE here
The object that contains the mutex should also own the condition variable. So the ThreadQueue code looks good. But it is unclear what dequeue() should return when an asynchronous stop is requested.
A common way to solve this is to introduce either a quit flag or a sentinel value to the queue itself, a stop() method and a way for dequeue() to signal a closed queue, for example, using std::optional<T> as return value.
template <typename T>
class ThreadQueue {
std::queue<T> q_;
std::mutex mtx;
std::condition_variable cv;
bool quit = false;
public:
void enqueue (const T& t) {
{
std::lock_guard<std::mutex> lck(mtx);
q_.push(t);
}
cv.notify_one();
}
std::optional<T> dequeue () {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [this] { return quit || !q_.empty(); });
if (quit) {
return {};
}
T t = q_.front();
q_.pop();
return t;
}
void stop() {
std::unique_lock<std::mutex> lck(mtx);
quit = true;
cv.notify_all();
}
};
Then when dequeue() returns an empty optional, the worker can exit gracefully.
void worker() {
std::cout << "[worker] starting..." << std::endl;
while (true) {
std::cout << "[worker] extract element from the queue" << std::endl;
auto el = my_queue.dequeue();
if (!el) {
std::cout << "[worker] exiting" << std::endl;
break;
}
std::cout << "[worker] consume extracted element" << std::endl;
std::cout << *el << std::endl;
}
std::cout << "[worker] exiting" << std::endl;
}
int main() {
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "[main] terminating..." << std::endl;
my_queue.stop();
t.join();
std::cout << "[main] terminated!" << std::endl;
return 0;
}
This is a quick hacky mod to your class to add stop function:
template <typename T>
class ThreadQueue {
std::queue<T> q_;
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> running = true;
public:
void enqueue (const T& t) {
{
std::lock_guard<std::mutex> lck(mtx);
q_.push(t);
}
cv.notify_one();
}
T dequeue () {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [this] { return !q_.empty() || !running; });
if (!running){return {};} // tidy-up part 1
T t = q_.front();
q_.pop();
return t;
}
bool is_running()
{
return running;
}
void stop()
{
running = false;
cv.notify_all(); // tidy-up part 2
}
};
see live example: https://godbolt.org/z/bje6Gj7o4
Obviously needs tidying up as you require

Is it safe to co_await and resume a coroutine on a different thread without synchronization?

cppreference has this example for co_await, which resumes the coroutine on a different thread.
But is it safe to do so without synchronization?
Specifically, resuming_on_new_thread() starts with one thread, then resumes after the co_await with another thread accessing the same coroutine "frame". Normally if you access the same data from two threads you need synchronization.
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out)
{
struct awaitable
{
std::jthread* p_out;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h)
{
std::jthread& out = *p_out;
if (out.joinable())
throw std::runtime_error("Output jthread parameter not empty");
out = std::jthread([h] { h.resume(); });
// Potential undefined behavior: accessing potentially destroyed *this
// std::cout << "New thread ID: " << p_out->get_id() << '\n';
std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
}
void await_resume() {}
};
return awaitable{&out};
}
struct task
{
struct promise_type
{
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
task resuming_on_new_thread(std::jthread& out)
{
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
std::jthread out;
resuming_on_new_thread(out);
}
Consider if the function has data:
task resuming_on_new_thread(std::jthread& out)
{
int data = 0; // Store some data using thread A.
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
std::cout << data << '\n'; // Access data with thread B. **Is this safe?**
}
Yes, it is safe. The constructor of std::jthread is the synchronisation.
It is safe. Remember that when a coroutine suspends, it returns control back to the caller. So only the thread that resumes the coroutine can continue the coroutine body execution. In your example, the coroutine starts in the main thread, then it suspends, then resumes on out.
A data race would occur only if you tried to resume the same coroutine from 2 different threads, which is undefined behaviour, as mentioned in the notes of std::coroutine_handle<Promise>::resume() here
The behavior is undefined if *this does not refer to suspended coroutine [...]. A concurrent resumption of the coroutine may result in a data race.

unique_ptr is not deleted after calling reset

This is my minimal, reproducible example
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
void do_func() {
while(true) {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
I was expecting the object to be deleted after reset is called. Even the code cannot exit because the while loop is blocking, which is understandable. I need to delete the object after a particular event, and cannot wait till the unique_ptr goes out of scope. If I change the do_func to
void do_func() {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(20));
}
then it is the expected behaviour.
Edit:
Based on your comments I have updated my code to
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
BaseClass() : x(1) {
dummy = std::make_shared<SomeClass>();
}
void do_func() {
while(true) {
std::cout << "doing stuff " << dummy->do_stuff(x) << std::endl;
x++;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
int x;
class SomeClass {
public:
int do_stuff(int x) {
return x * x;
}
};
std::shared_ptr<SomeClass> dummy;
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
And now the function does print garbage values. Does that mean I need to explicitly delete dummy in the destructor?
The simplest way to synchronize these two threads would be to use std::atomic_bool
#include <atomic>
class BaseClass {
public:
std::atomic_bool shouldContinueWork = true;
void do_func() {
while(shouldContinueWork) {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
obj->shouldContinueWork = false; //the thread will not do anything more after this, but the sleep will need to end on it's own
std::cout << "stopping work!" << std::endl;
// do not remove the object before join is called - you don't know if it will be still accessed from the other thread or not
// obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
// here it is safe to remove the `obj`, main thread is surely the only thread that accesses it
// (but it goes out of scope anyway)
return 0;
}
This solution doesn't take into account stopping the work midway (i.e. whole loop iteration must always be performed) and is generally prone to having a few more or less iterations of work - it should be precise enough when you have sleep of 1s, but with smaller sleep it won't guarantee any exact number of iterations, take that into account. std::condition_variable can be used for more precise control of thread synchronization.
Thanks for all your quick responses! Let me know if this is a good solution
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
BaseClass() : x(1) {
dummy = std::make_shared<SomeClass>();
}
virtual ~BaseClass() {
dummy.reset();
}
void do_func() {
while(dummy) {
std::cout << "doing stuff " << dummy->do_stuff(x) << std::endl;
x++;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
int x;
class SomeClass {
public:
int do_stuff(int x) {
return x * x;
}
};
std::shared_ptr<SomeClass> dummy;
};
class DerivedClass : public BaseClass {
};
int main() {
auto obj = std::make_unique<DerivedClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
The behaviour is now as expected.

param of asio timer async_wait difference lambda, bind, function pointer

When I use boost::asio::steady_timer, I find something difference between lambda, bind, function pointer.
#include <iostream>
#include <boost/asio.hpp>
void print() { std::cout << "Hello, world!" << std::endl; }
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print); // Error
t.async_wait([]{print();}) // Error
t.async_wait(std::bind(print)); // Done
io.run();
return 0;
}
I read asio manual, async_wait handler need const boost::system::error_code& error param. So if I changed print to void print(const boost::system::error_code & /*e*/), all things was right. But asio example of timer4/timer.cc && timeouts/server.cc used handler creating by bind without void print(const boost::system::error_code & /*e*/). When I changed to lambda, compile was wrong. So, what difference of signature been had between bind && lambda.
#include <iostream>
#include <functional>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context &io)
: timer_(io, boost::asio::chrono::seconds(1)), count_(0)
{
timer_.async_wait(std::bind(&printer::print, this));
}
~printer() { std::cout << "Final count is " << count_ << std::endl; }
void print()
{
if (count_ < 5) {
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() +
boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
// timer_.async_wait([this]{print();}); Error
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
io.run();
return 0;
}
The "partial" generated by std::bind detects and ignores arguments provided by the invocation point and not explicitly wired to the bound code.
A minimalistic example (godbolted):
#include <functional>
#include <iostream>
void callme(std::function<void(int, float)> arg) {
arg(42, 4.2);
}
// or like this
// template <typename F> void callme(F&& arg) {
// arg(42, 4.2);
// }
int main()
{
auto fn = std::bind([](){std::cout << "hi there" << std::endl; });
// auto fn = std::bind([](auto&& x){std::cout << "x=" << x << std::endl; }, std::placeholders::_1); <-- this works too and prints 42
// auto fn = std::bind([](auto&& x){std::cout << "x=" << x << std::endl; }, std::placeholders::_2); <-- and works too and prints 4.2
callme(fn);
return 0;
}

Thread safe std::cout

The following program still interleaves the output to std::cout. I tried to add a std::mutex to control access to std::cout via std::lock_guard, but it still interleaves.
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
std::mutex global_mtx{};
class Timer {
public:
Timer(size_t time, const std::function<void(void)>& f) : time{std::chrono::milliseconds{time}}, f{f} {}
~Timer() { wait_thread.join(); }
private:
void wait_then_call()
{
std::unique_lock<std::mutex> lck{mtx};
for(int i{10}; i > 0; --i) {
{
std::lock_guard<std::mutex>{global_mtx};
std::cout << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
}
cv.wait_for(lck, time / 10);
}
f();
}
std::mutex mtx;
std::condition_variable cv{};
std::chrono::milliseconds time;
std::function <void(void)> f;
std::thread wait_thread{[this]() {wait_then_call(); }};
};
int main()
{
auto f = []() {std::lock_guard<std::mutex>{global_mtx}; std::cout << "---------------- I waited to print! ----------------" << std::endl; };
Timer t1{3'000,f};
Timer t2{6'000,f};
Timer t3{2'000,f};
Timer t4{1'000,f};
}
Do I need to control access through a separate class or dedicated thread?
Your problem is here: std::lock_guard<std::mutex>{global_mtx}; creates a lock guard and immediately releases it. You need to create a variable to hold the lock, like std::lock_guard<std::mutex> lock{global_mtx};.
One way to prevent forgetting to name the lock is to make a lock object that you can use as an io manipulator:
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
std::mutex global_mtx{};
struct lockio
{
lockio(std::mutex& m) : lock_(m) {}
std::unique_lock<std::mutex> lock_;
};
std::ostream& operator<<(std::ostream& os, const lockio&) {
return os;
}
class Timer {
public:
Timer(size_t time, const std::function<void(void)>& f) : time{std::chrono::milliseconds{time}}, f{f} {}
~Timer() { wait_thread.join(); }
private:
void wait_then_call()
{
std::unique_lock<std::mutex> lck{mtx};
for(int i{10}; i > 0; --i) {
{
std::cout << lockio(global_mtx) << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
}
cv.wait_for(lck, time / 10);
}
f();
}
std::mutex mtx;
std::condition_variable cv{};
std::chrono::milliseconds time;
std::function <void(void)> f;
std::thread wait_thread{[this]() {wait_then_call(); }};
};
int main()
{
auto f = []() { std::cout << lockio(global_mtx) << "---------------- I waited to print! ----------------" << std::endl; };
Timer t1{3'000,f};
Timer t2{6'000,f};
Timer t3{2'000,f};
Timer t4{1'000,f};
}
Another (probably better) way is to create a little helper template function to wrap the protected operations:
#include <iostream>
#include <thread>
#include <condition_variable>
std::mutex global_mtx{};
template<class Mutex, class F>
decltype(auto) with_lock(Mutex &m, F &&f) {
std::lock_guard<Mutex> lock(m);
return f();
};
class Timer {
public:
Timer(size_t time, const std::function<void(void)> &f) : time{std::chrono::milliseconds{time}}, f{f} {}
~Timer() { wait_thread.join(); }
private:
void wait_then_call() {
std::unique_lock<std::mutex> lck{mtx};
for (int i{10}; i > 0; --i) {
with_lock(global_mtx, [&] {
std::cout << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
});
cv.wait_for(lck, time / 10);
}
f();
}
std::mutex mtx;
std::condition_variable cv{};
std::chrono::milliseconds time;
std::function<void(void)> f;
std::thread wait_thread{[this]() { wait_then_call(); }};
};
int main() {
auto f = []() {
with_lock(global_mtx, []
{
std::cout << "---------------- I waited to print! ----------------" << std::endl;
});
};
Timer t1{3'000, f};
Timer t2{6'000, f};
Timer t3{2'000, f};
Timer t4{1'000, f};
}
one more way:
#include <iostream>
#include <thread>
#include <condition_variable>
struct locked {
std::ostream& cout() const { return std::cout; }
std::ostream& cerr() const { return std::cerr; }
private:
static std::mutex& mutex() {
static std::mutex stdio_mutex;
return stdio_mutex;
}
std::unique_lock<std::mutex> lock_{mutex()};
};
class Timer {
public:
Timer(size_t time, const std::function<void(void)> &f) : time{std::chrono::milliseconds{time}}, f{f} {}
~Timer() { wait_thread.join(); }
private:
void wait_then_call() {
std::unique_lock<std::mutex> lck{mtx};
for (int i{10}; i > 0; --i) {
locked().cout() << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
cv.wait_for(lck, time / 10);
}
f();
}
std::mutex mtx;
std::condition_variable cv{};
std::chrono::milliseconds time;
std::function<void(void)> f;
std::thread wait_thread{[this]() { wait_then_call(); }};
};
int main() {
auto f = []() {
locked().cout() << "---------------- I waited to print! ----------------" << std::endl;
};
Timer t1{3'000, f};
Timer t2{6'000, f};
Timer t3{2'000, f};
Timer t4{1'000, f};
}
You create four Timer objects, each one having its own unique mutex object. So when t2 runs its thread, it locks its own mutex and it can because t1 locked a different mutex before beginning its loop.