If we would not have used std::async the serial unparallelized code could have looked as simple as that:
auto hist (histogram(input));
auto sorted_str (sorted( input));
auto vowel_count (vowels( input));
for (const auto &[c, count] : hist) {
cout << c << ": " << count << 'n';
}
cout << "Sorted string: " << quoted(sorted_str) << 'n';
cout << "Total vowels: " << vowel_count << 'n';
The only thing we did in order to parallelize the code was the following. We wrapped the three function calls into async(launch::async, ...) calls. This way these three functions are not executed by the main thread we are currently running in. Instead, async starts new threads and lets them execute the functions concurrently. This way we get to execute only the overhead of starting another thread and can continue with the next line of code, while all the work happens in the background:
auto hist (async(launch::async, histogram, input));
auto sorted_str (async(launch::async, sorted, input));
auto vowel_count (async(launch::async, vowels, input));
for (const auto &[c, count] : hist.get()) {
cout << c << ": " << count << 'n';
}
cout << "Sorted string: "
<< quoted(sorted_str.get()) << 'n'
<< "Total vowels: "
<< vowel_count.get() << 'n';
While histogram for example, returns us a map instance, async(..., histogram, ...) does return us a map that was wrapped in a future object before. This future object is kind of an empty placeholder until the thread that executes the histogram function returns. The resulting map is then placed into the future object so we can finally access it. The get function then gives us access to the encapsulated result.
Let's have a look at another minimal example. Consider the following code snippet:
auto x (f(1, 2, 3));
cout << x;
Instead of writing the preceding code, we can also do the following:
auto x (async(launch::async, f, 1, 2, 3));
cout << x.get();
That's basically it. Executing tasks in the background might have never been easier in standard C++. There is still one thing left to resolve: What does launch::async mean? launch::async is a flag that defines the launch policy. There are two policy flags which allow for three constellations:
| Policy choice | Meaning |
| launch::async | The function is guaranteed to be executed by another thread. |
| launch::deferred | The function is executed by the same thread, but later (lazy evaluation). Execution then happens when get or wait is called on the future. If none of both happens, the function is not called at all. |
| launch::async | launch::deferred | Having both flags set, the STL's async implementation is free to choose which policy shall be followed. This is the default choice if no policy is provided. |