We are going to implement a program that does multiple different things concurrently but instead of explicitly starting threads, we use std::async and std::future:
- First, we include all necessary headers and declare that we use the std namespace:
#include <iostream>
#include <iomanip>
#include <map>
#include <string>
#include <algorithm>
#include <iterator>
#include <future>
using namespace std;
- We implement three functions which have nothing to do with parallelism but do interesting tasks. The first function accepts a string and creates a histogram of all characters occurring within that string:
static map<char, size_t> histogram(const string &s)
{
map<char, size_t> m;
for (char c : s) { m[c] += 1; }
return m;
}
- The second function does also accept a string and returns a sorted copy of it:
static string sorted(string s)
{
sort(begin(s), end(s));
return s;
}
- The third one counts how many vowels exist within the string it accepts:
static bool is_vowel(char c)
{
char vowels[] {"aeiou"};
return end(vowels) !=
find(begin(vowels), end(vowels), c);
}
static size_t vowels(const string &s)
{
return count_if(begin(s), end(s), is_vowel);
}
- In the main function, we read the whole standard input into a string. In order to not segment the input into words, we deactivate ios::skipws. This way we get one large string, no matter how much white space the input contains. We use pop_back on the resulting string afterward because we got one string terminating '' character too much this way:
int main()
{
cin.unsetf(ios::skipws);
string input {istream_iterator<char>{cin}, {}};
input.pop_back();
- Now let's get the return values from all the functions we implemented before. In order to speed the execution up for very long input, we launch them asynchronously. The std::async function accepts a policy, a function, and arguments for that function. We call histogram, sorted, and vowels with launch::async as a policy (we will see later what that means). All functions get the same input string as arguments:
auto hist (async(launch::async,
histogram, input));
auto sorted_str (async(launch::async,
sorted, input));
auto vowel_count (async(launch::async,
vowels, input));
- The async calls return immediately because they do not actually execute our functions. Instead, they set up synchronization structures which will obtain the results of the function calls later. The results are now being calculated concurrently by additional threads. In the meantime, we are free to do whatever we want, as we can pick up those values later. The return values hist, sorted_str and vowel_count are of the types the functions histogram, sorted, and vowels return, but they were wrapped in a future type by std::async. Objects of this type express that they will contain their values at some point in time. By using .get() on all of them, we can make the main function block until the values arrive, and then use them for printing:
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';
}
- Compiling and running the code looks like the following. We use a short example string that does not really make it worth being parallelized, but for the sake of this example, the code is nevertheless executed concurrently. Additionally, the overall structure of the program did not change much compared to a naive sequential version of it:
$ echo "foo bar baz foobazinga" | ./async
: 3
a: 4
b: 3
f: 2
g: 1
i: 1
n: 1
o: 4
r: 1
z: 2
Sorted string: " aaaabbbffginoooorzz"
Total vowels: 9