C++'s <future>, making async C callbacks functions sync & enjoyable multi-threading
I use <future>
header very-very often in my C++ code everywhere at this point & wanted to share what's so cool in it.
Generally, I don't really like classic way of handling Thread
s, so much to worry about, spawning, running or joining. Even though, nearly every language has that POSIX-like API.
What I find more elegant is:
- C#/.NET's
async
,await
orTask
- JavaScript's
async
,await
orPromise
(I know JS is single-threaded, but still) - Dart's
async
,await
orFuture
(again, not actually "multi-threading").
I got to know about std::future
and std::promise
in C++ 11 few years back & they're quite the same in terms of how they're used in code. My happiness couldn't be greater.
Just see how elegant this looks:
Code
Use std::async
function to spawn a new thread and pass a function to it.
#include <future>
#include <iostream>
int32_t main() {
auto future = std::async([&]() {
std::cout << "A std::future came to life!\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Hello from std::future & I'm 2 seconds late!\n";
});
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Hey, I'll be first on the console!\n";
return 0;
}
Don't let that capturing lambda confuse you.
Something like this will be up on your terminal:
A std::future came to life!
Hey, I'll be first on the console!
Hello from std::future & I'm 2 seconds late!
warning
Always keep a reference accessible to the std::future
returned by the std::async
.
Otherwise, your function passed will not launch on separate thread & just execute sync-ly.
The other day, I was spawning std::future
s to do various things concurrently in a for-loop
and noticed that everything was being executed synchronously. Then I came to know that I need to store their reference somewhere to be able to access them later. Added a std::vector<std::future<void>>
in my case (where I pushed all returned std::future
s).
Here let's just remove the future
variable from above code:
#include <future>
#include <iostream>
int32_t main() {
std::async([&]() {
std::cout << "A std::future came to life!\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Hello from std::future & I'm 2 seconds late!\n";
});
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Hey, I'll be first on the console!\n";
return 0;
}
Notice how "Hey, I'll be first on the console!" came in last. Something we didn't expect.
Takes away the whole purpose.
A std::future came to life!
Hello from std::future & I'm 2 seconds late!
Hey, I'll be first on the console!
Equivalents in HLL Dart or JavaScript
How simple is that. A separate thread launched with std::async
& do whatever with it. This syntax is quite nice & familiar to how I'm trained with Dart/JavaScript.
Take equivalent Dart for example:
void main() async {
() async {
print('An async function came to life!');
await Future.delayed(const Duration(seconds: 2));
print('Hello from async function & I\'m 2 seconds late!');
}();
await Future.delayed(const Duration(seconds: 1));
print('Hey, I\'ll be first on the console!');
}
An async function came to life!
Hey, I'll be first on the console!
Hello from async function & I'm 2 seconds late!
This std::future
stuff in C++ now may seem really cool to you, but I still haven't noticed any analog of Promise.all
from JavaScript in it yet.
Though, simple thread create/start/join API, is also good in some situations like doing event-polling in background or just spinning up something consistently while UI is visible. Other than that, it just becomes a pain in my opinion to handle if we have a lot of async operations to worry about & handling of tasks.
Making a C async callback based function sync
If you've written JavaScript, I think you know how some libraries still ask for a function as argument and call it back once some heavy/network-related operation is done. This reminds me of fs
(callback based filesystem API) and fs/promise
(Promise
s based filesystem API) from Node.js. Converting a old callback based API into shiny new async API is something I would do to sooth my soul.
An equivalent for this exists in Dart to make a function sync: Completer.
I had a similar situation in C/C++, where I needed to "wait" for some async operation to finish before I could return the result from my function. I didn't know any solution but std::promise
from <future>
came in very-very handy.
Let's take this code:
We want to wait for the value that is being returned from a_long_running_function_that_returns_result_in_callback
inside callback from another thread before proceeding to print "This should not execute early!!!\n"
on console.
#include <future>
#include <iostream>
// Let's say we have this function which takes a lot of time to execute &
// returns the final result in the passed [callback] from another thread.
// I'm sure a lot of C/C++ librares like this are in existence.
//
// We need to wait for the result outside the passed callback.
//
// A place to demonstrate awesomeness of `std::promise`.
void a_long_running_function_that_returns_result_in_callback(
void (*callback)(int)) {
std::thread([=]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
callback(69420);
}).detach();
}
void print_the_value(int value) {
std::cout << "The value is: " << value << "\n";
}
void main() {
a_long_running_function_that_returns_result_in_callback(print_the_value);
std::cout << "This should not execute early!!!\n";
std::cin.get();
}
Output of this on your terminal will be:
This should not execute early!!!
The value is: 69420
The order is clearly messed up.
Let's bring std::promise
to the rescue:
#include <future>
#include <iostream>
// Don't kill me for using global variables. C'mon it's C API that we're trying
// to wrap.
auto g_promise = std::promise<int>();
// Let's say we have this function which takes a lot of time to execute &
// returns the final result in the passed [callback] from another thread.
// I'm sure a lot of C/C++ librares like this are in existence.
//
// We need to wait for the result outside the passed callback.
//
// A place to demonstrate awesomeness of `std::promise`.
void a_long_running_function_that_returns_result_in_callback(
void (*callback)(int)) {
std::thread([=]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
callback(69420);
}).detach();
}
void print_the_value(int value) {
std::cout << "The value is: " << value << "\n";
// Set the value to the `std::future` corresponding to the promise.
// Cause the wait to be over.
g_promise.set_value(value);
}
void main() {
a_long_running_function_that_returns_result_in_callback(print_the_value);
// Wait until the promise is resolved.
g_promise.get_future().wait();
std::cout << "This should not execute early!!!\n";
std::cin.get();
}
Notice how std::promise::set_value
is called from the callback.
Then, std::future::wait
is called from the main thread to wait for the promise to be resolved.
Such a nice syntax. If you wish to get the value inside std::promise
from the callback, you can use std::future::get
instead of the std::future::wait
.
int a_long_running_function_that_returns_same_result_without_callback() {
a_long_running_function_that_returns_result_in_callback(print_the_value);
// Wait until the promise is resolved & return from this wrapper function.
return g_promise.get_future().get();
}
void main() {
std::cout
<< "Returned value: "
<< a_long_running_function_that_returns_same_result_without_callback()
<< "\n";
std::cin.get();
}
There are also following things to note, which one may find useful:
Don't spam Rust is better in comments (oh, there are no comments right now). I'm staying with C++ for now.