Search code examples
c++boostboost-asioc++-coroutineboost-coroutine

Switch context in coroutine with boost::asio::post


I'm trying to understand C++ coroutines. My expectation in the example below would be, that each asio::post will switch the context/thread to the given threadpool. But something very strange happens. I get the following output:

thread0: 7f0239bfb3c0
thread2: 7f02391f6640 (tp2)
thread3: 7f02387f5640 (tp)
thread4: 7f02387f5640 (tp2)
thread5: 7f02373f3640 (tp)
thread6: 7f02373f3640 (tp2)
thread7: 7f0235ff1640 (tp)
done

So the thread-id of 3 and 4 are the same, but they should run on a different context/threadpool. And 3, 5 and 7 should have the same ID since it's the same context (with just one thread). I assume that I understand some concept wrong. Can you give me a hint?

Thanks

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

boost::asio::thread_pool tp {1};
boost::asio::thread_pool tp2 {10};

boost::asio::awaitable<void> test() {
  std::cout << "thread2: " << boost::this_thread::get_id() << std::endl;
  co_await boost::asio::post(tp, boost::asio::use_awaitable);
  std::cout << "thread3: " << boost::this_thread::get_id() << std::endl;
  co_await boost::asio::post(tp2, boost::asio::use_awaitable);
  std::cout << "thread4: " << boost::this_thread::get_id() << std::endl;
  co_await boost::asio::post(tp, boost::asio::use_awaitable);
  std::cout << "thread5: " << boost::this_thread::get_id() << std::endl;
  co_await boost::asio::post(tp2, boost::asio::use_awaitable);
  std::cout << "thread6: " << boost::this_thread::get_id() << std::endl;
  co_await boost::asio::post(tp, boost::asio::use_awaitable);
  std::cout << "thread7: " << boost::this_thread::get_id() << std::endl;
}

int main() {
  std::cout << "thread0: " << boost::this_thread::get_id() << std::endl;

  boost::asio::co_spawn(tp2, &test, [](std::exception_ptr e) { std::cout << "done" << std::endl; });

  tp2.join();
  tp.join();
}

Solution

  • I have a near identical answer up here: asio How to change the executor inside an awaitable?

    I'll leave the explanation there, but your code can act as expected like this:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iomanip>
    #include <iostream>
    #include <sstream>
    #include <thread>
    namespace asio = boost::asio;
    
    asio::thread_pool tp1{1}, tp2{1};
    
    static inline auto trace(auto msg) {
        static std::mutex mx;
        std::lock_guard lg(mx);
        std::cout //
            << "trace " << msg << ": thread " << std::hex << std::setw(2)
            << std::setfill('0')
            << (std::hash<std::thread::id>{}(std::this_thread::get_id()) % 256)
            << std::endl;
    }
    
    asio::awaitable<void> test() {
        trace(2);
        auto a1 = asio::bind_executor(tp1.get_executor(), asio::use_awaitable);
        auto a2 = asio::bind_executor(tp2.get_executor(), asio::use_awaitable);
        co_await post(a1); trace(3);
        co_await post(a2); trace(4);
        co_await post(a1); trace(5);
        co_await post(a2); trace(6);
        co_await post(a1); trace(7);
    }
    
    int main() {
        post(tp1, [] { trace("tp1"); });
        post(tp2, [] { trace("tp2"); });
    
        co_spawn(tp2, test, [](std::exception_ptr const&) {
            std::cout << "done" << std::endl;
        });
    
        tp2.join();
        tp1.join();
    }
    

    Prints e.g.

    trace tp2: thread 33
    trace 2: thread 33
    trace tp1: thread d6
    trace 3: thread d6
    trace 4: thread 33
    trace 5: thread d6
    trace 6: thread 33
    trace 7: thread d6
    done