I currently have some synchronous C++ code that carries out some time-consuming tasks in a sequential fashion. I'm considering using Boost coroutines to refactor this into parallel tasks.
The core of the tasks is a call to an external library that provides either a synchronous API (which it is currently using) or an asynchronous API that has to be periodically polled to both progress the action and determine whether it has completed. The API is not thread-safe, but if the asynchronous version is used it can cope with multiple requests happening concurrently as long as the polling API itself is not called concurrently.
(Since I don't want any thread shifting, it seems like I should use the coroutine API directly instead of using the ASIO wrappers or similar, but I'm willing to be persuaded otherwise. I'm also using Boost 1.55 at the moment, so the Fiber library is not available; but it also seems like overkill for this usage.)
My main question with coroutines at the moment is that I'm not sure how to implement throttling on the polling -- say that I have 100 tasks to run in parallel; I want it to do one polling run through all 100 tasks and then sleep the thread for a specified time. Some of the tasks may complete sooner than others, so later on it might still be polling 40 tasks per loop and then sleeping the same amount of time. I don't want it to ever poll the same task twice in a row without an intervening sleep.
Ok, turned out to be a lot easier than I thought. This is what I've ended up with so far:
typedef boost::coroutines::coroutine<void>::push_type coroutine;
typedef boost::coroutines::coroutine<void>::pull_type yield_context;
typedef std::vector<coroutine> coroutine_list;
bool run_once_all(coroutine_list& coros)
{
bool pending = false;
for (auto& coro : coros)
{
if (coro)
{
coro();
pending = pending || !coro.empty();
}
}
return pending;
}
void run_all(coroutine_list& coros,
const boost::chrono::nanoseconds& idle_ns)
{
while (run_once_all(coros))
{
boost::this_thread::sleep_for(idle_ns);
}
}
void run_all(coroutine_list& coros, yield_context& yield)
{
while (run_once_all(coros))
{
yield();
}
}
The idea is that you make a coroutine_list
, then emplace_back
lambda functions with signature void (yield_context&)
, then run_all
. The first one is for the top level and the second one is for nested coroutines.
Seems to work, although I haven't given it a major workout yet. Actually a little surprised something like this isn't in the library already.