Boost logo

Boost Users :

From: accelerator0099 (accelerator0099_at_[hidden])
Date: 2023-12-14 09:44:20


Consider the producer-consumer model:

producer -+- consumer1
 Â Â Â Â Â Â Â Â Â  +- consumer2
 Â Â Â Â Â Â Â Â Â  +- consumer3
 Â Â Â Â Â Â Â Â Â  +- consumer4
 Â Â Â Â Â Â Â Â Â  +- consumer5

The producer periodly push items into a queue, and those consumers wait
on that queue for items

It's easy to implement in a synchronous, multi-threaded environment: the
producer just locks the queue and push into it, and notify one consumer
through a condition variable. Consumers waiting on that condition
variable will be woken one by one, popping items and consume them

How to do those in a asynchronous environment?

Consumers async_pop() on that queue, posting their completion tokens
into the executor. When there are items available, the executor notify
one consumer by calling its completion token

Is it right?

How do the producer tell the executor "The asynchronous operation is
done, You should call that completion token"?

My implemention of a possible async queue (based on timers):

#include <boost/asio.hpp>
#include <deque>
#include <iostream>

using namespace std::literals;
namespace io = boost::asio;
using io::awaitable;
auto& uawait = io::use_awaitable;

template<class D>
awaitable<void> delay(const D& dur) {
 Â Â Â  io::steady_timer tm{ co_await io::this_coro::executor };
 Â Â Â  tm.expires_after(dur);
 Â Â Â  co_await tm.async_wait(uawait);
}

template<class T>
class aqueue {
 Â Â Â  std::deque<T> mq;
public:
 Â Â Â  using reference = T&;
 Â Â Â  using const_reference = const T&;
 Â Â Â  size_t size() const noexcept { return mq.size(); }
 Â Â Â  bool empty() const noexcept { return mq.empty(); }

 Â Â Â  awaitable<T> async_pop() {
 Â Â Â Â Â Â Â  while (empty()) {
 Â Â Â Â Â Â Â Â Â Â Â  co_await delay(1ms);
 Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â  auto t = std::move(mq.front());
 Â Â Â Â Â Â Â  mq.pop_front();
 Â Â Â Â Â Â Â  co_return t;
 Â Â Â  }

 Â Â Â  awaitable<void> async_push(T t) {
 Â Â Â Â Â Â Â  mq.push_back(std::move(t));
 Â Â Â Â Â Â Â  co_return;
 Â Â Â  }
};

class consumer {
public:
 Â Â Â  const int id;
 Â Â Â  aqueue<int>& queue;

 Â Â Â  consumer(int _i, aqueue<int>& _q) : id{ _i }, queue{ _q } {}
 Â Â Â  awaitable<void> operator()() {
 Â Â Â Â Â Â Â  std::cout << "Consumer " << id << " started\n";
 Â Â Â Â Â Â Â  auto i = co_await queue.async_pop();
 Â Â Â Â Â Â Â  std::cout << "Consumer " << id << " got " << i << '\n';
 Â Â Â  }
};

int main() {
 Â Â Â  io::io_context ctx;
 Â Â Â  aqueue<int> aq;
 Â Â Â  auto producer = [&]() -> awaitable<void> {
 Â Â Â Â Â Â Â  std::cout << "Producer started\n";
 Â Â Â Â Â Â Â  for (int i{}; i != 5; ++i) {
 Â Â Â Â Â Â Â Â Â Â Â  co_await delay(1s);
 Â Â Â Â Â Â Â Â Â Â Â  co_await aq.async_push(i);
 Â Â Â Â Â Â Â  }
 Â Â Â  };
 Â Â Â  io::co_spawn(ctx, producer, io::detached);
 Â Â Â  for (int i{}; i != 5; ++i)
 Â Â Â Â Â Â Â  io::co_spawn(ctx, consumer{ i, aq }, io::detached);
 Â Â Â  std::cout << "RUN\n";
 Â Â Â  ctx.run();
 Â Â Â  return 0;
}

Is it a proper way to do so using timers? I think there should be a
better way


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net