c++c++20c++-coroutine

Why is this asynchronous C++ coroutine always being run on the same thread?


After running this program

std::future<int> f() {
    std::cout << "enter f on thread " << std::this_thread::get_id() << std::endl;
    co_return 1;
}
std::future<void> g() {
    std::future<int> i = co_await std::async(f);
    std::cout << "resume g on thread: " << std::this_thread::get_id() << std::endl;
    co_return;
}
int main() {
    std::future<void> f;
    while (true) {
        f = g();
        std::this_thread::sleep_for(1s);
    }
    return 0;
}

which has been compiled with MSVC 19.43 using the /await option, I observed that function f is always being run on the same thread whereas the continuation in function g is always run on a newly created thread. I also tried to call std::async with std::launch::async option to force the creation of a new thread, but still the same same thread would be used to run f. Can someone explain why?


Solution

  • std::future shouldn't have a promise type in C++20 coroutines, but that's an MSVC extension.


    std::async(f); executes the function f in another thread, all mainstream implementations use a thread-pool for std::async, quoting cppref.

    (potentially in a separate thread which might be a part of a thread pool)

    you would get the same thread id with a normal function, this has nothing to do with coroutines. you could get one of N threads in the pool, usually N is the number of cores, looks like MSVC pool uses the same thread every time when it is not busy.


    whereas the continuation in function g is always run on a newly created thread.

    yes, MSVC implementation of std::future awaiter creates a new thread to run the continuation every time, the following code is from MSVC standard library.

     void await_suspend(experimental::coroutine_handle<> _ResumeCb) {
         // TRANSITION, change to .then if and when future gets .then
         thread _WaitingThread([&_Fut = _Fut, _ResumeCb]() mutable {
             _Fut.wait();
             _ResumeCb();
         });
         _WaitingThread.detach();
     }
    

    a thread is launched and detatched, that's waiting to run the continuation, this happens when awaiting an std::future


    to summarize what's happening:

    1. main calls g, first part of g runs in main thread until the await.
    2. std::async(f) runs f in the threadpool.
    3. await future launches a new thread to wait on the future then run the continuation of g