C++ futures part 1: The mental model

Posted by ibobev 2 days ago

Counter69Comment16OpenOriginal

Comments

Comment by ahartmetz 21 hours ago

This seems to omit the main point of it, though? That the compiler turns a function into a state machine that can restart from any of its suspension points. I guess that's the "state" variable, but what I'm talking about is what it contains and the partially compiler-generated code that deals with it.

C++ async is unfortunately a "bring your own runtime" sort of model (for now), but it does handle the part that would be incredibly tedious by hand, building above mentioned state machine.

Comment by jcranmer 20 hours ago

That's the co_await/co_yield/co_routine stuff, which this isn't talking about--this is talking about the std::future-et-al logic, which predates C++ coroutines by almost a decade.

Comment by rramadass 7 hours ago

One point to note is that "concurrency support" is part of the std library (https://cppreference.com/cpp/thread) while "coroutine support" is part of the language/compiler runtime (https://cppreference.com/cpp/language/coroutines and https://cppreference.com/cpp/coroutine).

Comment by kccqzy 20 hours ago

You are confusing it with coroutines. This is just a plain library feature that the compiler doesn’t necessarily have special support for.

Comment by jandrewrogers 19 hours ago

If the objective with using async is performance and scalability then you should almost always "bring your own runtime". Bringing your own runtime is the main reason you might want to use async.

Delegating the runtime nerfs many of the benefits that async can offer. If you are going to delegate the runtime it offers relatively little compared to just using threads.

Comment by alexeiz 19 hours ago

C++ async model is too complicated (and not cost free as claimed, since it allocates memory for the coroutine state) and as such it wasn't widely adopted. I don't see any code using C++ async (apart from libraries implementing "tasks" in various forms). At this point I view it as a failed experiment. My bet is that executors are going to replace async completely.

Comment by jandrewrogers 19 hours ago

> since it allocates memory for the coroutine state

Heap allocation is not required, just the default.

You can do static allocation if you wish, though it is a little more complicated than typical static allocation due to the relative unpredictability.

Comment by pjmlp 14 hours ago

Nah, it was originally based on .NET async/await model, having been proposed by Microsoft, and initially experienced in WinRT/UWP async runtime.

So anyone that has experience in such model, including the awaitable magic methods, can grasp it.

While not cost free, the optimisers are expected to elide the cost most of the time.

Comment by larodi 13 hours ago

how's this in the news, I mean I don't get it. C++ had futures for at least 10 years now?

Comment by rramadass 7 hours ago

Not the point.

C++ provides quite a broad set of features for concurrency support (https://cppreference.com/cpp/thread). The task-based paradigm (i.e. promise/future etc.) is a logical framework which is rather confusing to folks who come from a straight thread-based (think pthreads) background.

Raymond Chen in his own inimitable style makes things clear so one can see that the logical task-based framework is nothing more than a higher level abstraction above thread-based core routines i.e. there is no underlying magic involved.

Using the task-based api makes your code clearer and portable since you are dealing with only logical dataflows amongst your problem-domain "tasks" and not any underlying OS/thread-library/machine level complexities.

Comment by thefourthchime 20 hours ago

Wow, I had no idea Raymond Chen was still doing blog posts. I used to love his posts back in the day.

Comment by afdbcreid 20 hours ago

He writes regularly. Sometimes explaining C++, rarely explaining C#, sometimes historic stories, sometimes current stories, etc..

Comment by gsliepen 18 hours ago

> The wait_for method is why we have a condition variable at all. If it weren’t for that guy, we could have used std::atomic‘s wait and notify_all methods.

No, the std::mutex is the reason you have a condition variable. Never mix std::atomic with std::mutex-and-friends, you are very likely introducing race conditions that way.

Comment by lukaslalinsky 17 hours ago

I think you are misunderstanding what the article suggests. std::atomic::wait and std::atomic::notify_all are the C++ abstractions over futexes. The whole concept of the future can be expressed in terms of atomics and futexes. The only problem is that the C++ standard doesn't have std::atomic::wait_for.

Comment by gsliepen 16 hours ago

You can indeed, but it would change the code from the article drastically: you have to get rid of the mutex, the discriminant becomes an atomic variable, and you need barriers between access to the discriminant and to the value the source is holding. Performance-wise I don't think it will show a drastic improvement, the main benefit would be the size of the source.

Comment by 18 hours ago

Comment by harrouet 4 hours ago

Why not use std::variant instead of an enum and a collection of ifs ?