1

Effective Modern C++ recommends using void futures as a way to communicate one-off events from one thread to another. Below is a minimal reproduction of that practice.

std::promise<void> promise;
void detect()
{
  std::thread latent([]
  {
    promise.get_future().wait();
    // react
  }

  //...do some work
  promise.set_value();
}

The book suggests this is preferable to busy waiting in latent, because the call to wait is a blocking call that causes the waiting thread to be suspended.

How does the blocking work internally (assuming a x86 architecture, and 64-bit Windows OS)? Is it coordinated by the hardware, the operating system, or both? In other words, doesn't there have to be a busy wait somewhere in order to determine when the wait condition is fulfilled? If so, why is this better than busy waiting in the application?

I have read this, which seemed to provide circular answers (e.g. "it waits until its work is done" - but how does it wait?).

7
  • Note that an ordinary method/function call, in the absence of a promise/future and .wait(), is also considered a "blocking call." Jun 17, 2021 at 21:46
  • So your .wait() call could merely be considered an ordinary method call, in this context. What is it waiting for? The result of the promise/future to arrive. Jun 17, 2021 at 21:47
  • What, exactly, is the mechanism under the hood that accomplishes this, I'm not sure. It could be as simple as a callback. In any case, the source code for std::future is here, if you're so inclined. Jun 17, 2021 at 21:53
  • Fair point - I am trying to understand the distinction (that exists in my mind, at least) between blocking calls that are doing things (e.g., adding numbers) vs. blocking calls that are not doing any work of their own but are instead waiting for work to finish elsewhere.
    – msmoiz
    Jun 17, 2021 at 21:56
  • Note the #include <mutex> at line 38, which is the first mechanism I thought of that might accomplish this. Jun 17, 2021 at 21:57

1 Answer 1

4

A blocking call works however it is that the OS and implementation want them to work.

doesn't there have to be a busy wait somewhere in order to determine when the wait condition is fulfilled?

That depends on how you define "busy wait".

If you were to, for example, check the value of an atomic in a loop to see if a thread should proceed, that is typically what is referred to as a "busy wait". Your thread is just sitting there until it can proceed. Wouldn't it be great if the thread of execution would, instead of sitting in a loop, just stop executing until that atomic value gets changed?

If that could happen, then that means CPU computational resources (and possibly battery life) wouldn't be wasted sitting there doing nothing. The CPU computational time could be spent on a thread that's actually doing something, or maybe the CPU could under-clock itself so as not to waste power on a task that isn't doing much. Or whatever.

But the OS doesn't know that it could just stop executing your thread. To the OS, a thread that's sitting in a loop is no different from a thread that is doing useful work. The OS can't tell, so it will treat your busy-wait just like any other task.

If you want to surrender your computational resources to the OS so it can find something useful to do, you must do it in a way the OS recognizes as a waiting operation. OS synchronization primitives like mutex locks are places where the OS can recognize not just that your thread is waiting, but it can also recognize what it is waiting on. This allows the OS to halt your thread of execution when you wait and wake it up ASAP once that mutex is released. There are other OS primitives that can do that too.

What's going on behind the scenes in future::wait is something like that. The shared state of a promise/future pair have some kind of OS-recognized synchronization primitive. To wait on the future means to block on that primitive, thus surrendering your thread's execution in a way the OS recognizes as waiting on a particular thing. Once the promise is fulfilled, the synchronization primitive is released and your thread can proceed.

You could call that a "busy wait", but that's not how the term is typically used.

3
  • Interesting, thanks for the detailed explanation. It begs the question, though: doesn't the OS have to use computational resources to evaluate the mutex lock or other sync primitive? Is the point that the OS will spend less resources on resolving the condition than a spin-locked thread (however implemented)?
    – msmoiz
    Jun 18, 2021 at 22:17
  • @msmoiz: "doesn't the OS have to use computational resources to evaluate the mutex lock or other sync primitive?" That depends on how you define "resources". Yes, the OS has to store what thread(s) are blocked on a synchronization primitive. But that's not a busy-wait; it's just some data structures stored somewhere the OS has access to. As far as the CPU is concerned, it's off doing something unrelated. Jun 18, 2021 at 22:44
  • You could call it a "busy wait" in the same way you could call it a "potato peeler" - it's not one, but you won't be struck by lightning for lying.
    – user253751
    Jun 29, 2021 at 9:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.