Home > Enterprise >  What will happen if boost::asio::post is called when boost::asio::thread_pool::join is already worki
What will happen if boost::asio::post is called when boost::asio::thread_pool::join is already worki

Time:05-30

I have a class wrapper for boost::asio::thread_pool m_pool. And in wrapper's destructor i join all the threads:

ThreadPool::~ThreadPool()
{
    m_pool.join();
    cout << "All threads in Thread pool were completed";
}

Also i have queue method to add new task to threadpool:

void ThreadPool::queue(std::function<void()> task, std::string label)
{
    boost::asio::post(m_pool, task);
    cout << "In Thread pool was enqueued task: " << label;
}

What will happen if boost::asio::post is called by another thread, when thread_pool is waiting for join all threads in destructor?

Is this behavior defined? What will happen? Will be the task finished? I guess it may not. But i'm not sure.

CodePudding user response:

Yes, new handlers can be posted while a join is pending. join will keep waiting until the execution context runs out of work. This could be indefinite.

In fact, it is often indefinite e.g. in the case of a network server. The execution context will typically only "exit" once the server stops listening and all the connected sessions are closed.

In other words, join() is a synchronizing/rendez-vous operation, not a stop().

The simplest example to show that/how this works:

Live On Coliru

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
using boost::asio::thread_pool;

// convenience
using namespace std::chrono_literals;
static auto now = std::chrono::steady_clock::now;
static auto start = now();

static inline void trace(auto const&... msg) {
    std::cout << std::setw(5) << (now() - start) / 1ms << "ms - ";

    (std::cout << ... << msg) << std::endl;
}

void do_loop(int const i, thread_pool::executor_type ex) {
    std::this_thread::sleep_for(1s);
    trace("work ", i);

    if (i < 10)
        post(ex, std::bind(do_loop, i   1, ex));
}

int main() {
    boost::asio::thread_pool io;

    post(io, std::bind(do_loop, 0, io.get_executor()));

    trace("Start join");
    io.join();
    trace("Complete join");
}

This prints e.g.

    0ms - Start join
 1001ms - work 0
 2001ms - work 1
 3001ms - work 2
 4002ms - work 3
 5002ms - work 4
 6002ms - work 5
 7002ms - work 6
 8003ms - work 7
 9003ms - work 8
10003ms - work 9
11004ms - work 10
11004ms - Complete join

CAVEAT

Note that if the first async work is posted from another thread you may need additional work to prevent the pool from joining prematurely.

Without any synchronization, you wouldn't be able to tell in what order things execute: whether the post happens-before the join.

You can use an executor_work_guard (see make_work_guard) in such cases.

The same caveat applies to hybrid scenarios, where some async call chains might terminate, but unrelated threads are still posting tasks without synchronization. The order in which these operations happen is undeterministic (OS/implementation defined) and it may happen to extend the life of the pool.

Again

  • make sure of work to guarantee the task will still execute.
  • keep a separate flag (e.g. an std::atomic_bool) indicating that no more work is being accepted by the pool

Is this documented

Yes:

This function blocks until the threads in the pool have completed. If stop() is not called prior to join(), the join() call will wait until the pool has no more outstanding work.

  • Related