Concurrency TS - Editor's Report - Artur Laksberg - CppCon 2015
Concurrency TS - Editor's Report - Artur Laksberg - CppCon 2015
Concurrency TS - Editor's Report - Artur Laksberg - CppCon 2015
Editor’s Report
Artur Laksberg
[email protected]
Microsoft Corp.
2015
Technical Specification for C++ Extensions for Concurrency
2
What’s in it?
Improvements to std::future
Latches and Barriers
Atomic smart pointers
3
Part 1: Improvements to std::future
4
std::future Refresher
std::future<T> -- a proxy for an eventual value of type T
std::promise<T> -- a one-way channel to set the future.
shared
future promise
state<T>
5
To Block or Not To Block?
#include <future>
void process()
{
future<string> f = read_string_from_file();
// Blocking call:
string s = f.get();
use(s);
// Non-blocking call:
f.then([](future<string> result) {
string s = result.get();
use(s);
});
6
}
The then returns a future!
#include <future>
void process()
{
future<string> f = read_string_from_file();
7
Implicit Unwrapping
future<string> download_html(string url);
void process()
{
future<string> f = read_string_from_file();
future<future<string>> future<string> 8
Chaining With Implicit Unwrapping
void process()
{
future<string> f = read_string_from_file();
f.then([](future<string> result) {
string url = result.get();
return download_html(url);
}).then([](future<string> result) {
return result.get().length();
});
}
9
Join and Choice
Join: create a future that becomes ready when all parameter futures
become ready.
Choice: create a future that becomes ready when any of parameter
futures becomes ready
10
Join Example (Homogeneous)
vector<future<int>> futures;
future<vector<future<int>>> ready =
when_all(futures.begin(), futures.end());
ready.then([](future<vector<future<int>>> result) {
vector<future<int>> v = result.get();
for(auto& f : v) {
assert(f.is_ready());
}
});
11
Join Example (Heterogeneous)
future<int> fi = ...;
future<tuple<future<int>, future<char>>>
future<char> fc = ...;
ready.then([](auto result) {
auto t = result.get();
future<int> fi = get<0>(t);
future<char> fc = get<1>(t);
assert(fi.is_ready());
assert(fc.is_ready());
12
});
Choice Example (Homogeneous)
int main()
{
vector<future<int>> futures; future<when_any_result<vector<future<int>>>>
ready.then([](auto result) {
auto wa = result.get();
future<int> f = wa.futures[wa.index];
assert(f.is_ready());
});
}
13
Choice Example (Heterogeneous)
int main()
{ future<when_any_result<tuple<future<int>,future<char>>>>
future<int> fi = ...;
future<char> fc = ...;
ready.then([](auto result) {
auto wa = result.get();
if (wa.index==0) {
future<int> fi = get<0>(t);
assert(fi.is_ready());
}
else{
assert(wa.index==1);
future<char> fc = get<1>(t);
assert(fc.is_ready());
} 14
});
}
Read From File
Reading asynchronously from a file, one string at a time
class file_reader
{
public:
file_reader(string);
bool is_eof();
future<string> get_next_string();
};
15
Read From File, Synchronously
void read_sync()
{
file_reader reader("myfile.txt");
while (!reader.is_eof())
{
auto result = reader.get_next_string();
auto s = result.get();
cout << s << "\n";
}
}
16
Read From File, Asynchronously
void read_sync()
{
file_reader reader("myfile.txt");
while (!reader.is_eof())
{
auto result = reader.get_next_string();
auto s = result. then([&](auto result){
auto s = result.get();
cout << s << "\n";
...huh...?
} 17
}
}
Read From File, Two Lines
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
});
}
return make_ready_future();
});
}
return make_ready_future(); 18
}
Read From File, Three Lines
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
});
}
return make_ready_future();
});
}
return make_ready_future();
});
} 19
return make_ready_future();
}
Find Commonalities…
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
});
}
return make_ready_future();
});
}
return make_ready_future(); 20
}
Recursion To The Rescue
template<typename Func>
future<void>
read_async(shared_ptr<file_reader> reader, Func func)
{
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
func(std::move(result));
return read_async(reader, func);
});
}
return make_ready_future();
}
21
Let’s Generalize It!
template<typename Pred, typename Iter, typename Func>
future<void>
make_iterative_future(Pred predicate, Iter iter, Func func)
{
if (!predicate()) {
return iter().then([=](auto result) {
func(std::move(result));
return make_iterative_future(predicate, iter, func);
});
}
return make_ready_future();
}
22
Invoking It…
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
23
Complicated?
sync async
void read_sync() template<typename Pred, typename Iter, typename Func>
{ future<void> make_iterative_future(Pred predicate, Iter iter, Func func)
file_reader reader("myfile.txt"); {
if (!predicate()) {
while (!reader.is_eof()) return iter().then([=](shared_future<string> result) {
{ func(std::move(result));
auto result = reader.get_next_string(); return make_iterative_future(predicate, iter, func);
auto s = result.get(); });
cout << s << "\n"; } }
} return make_ready_future();
}
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
24
Coming to C++ v.next: await
future<void> read_async()
{
file_reader reader("myfile.txt");
while (!reader.is_eof())
{
auto result = reader.get_next_string();
auto s = result.get() await result;
cout << s << "\n";
}
}
25
Part 2: Latches and Barriers
26
Signal an Event (Incorrect!)
condition_variable cv; Only used in
mutex m; one thread?
bool is_condition_satisfied();
27
Fails to account for spurious Will be ignored before cv
wakeups enters wait, causing deadlock
Signal an Event (Corrected)
condition_variable cv;
mutex m;
bool flag = false;
bool is_condition_satisfied();
30
Latch and Barrier
Latch: A thread coordination mechanism that allow one or more
threads to block until an operation is completed.
Barrier: Like latch, but can be reused.
Flex Barrier: Allow additional behavior to be defined when an
operation has completed.
31
Using a Latch to Signal and Event
latch l(1);
bool is_condition_satisfied();
32
Wait for All Tasks To Finish
template<typename F>
void start_task(F&&func);
void do_work()
{
latch completion_latch(task_count);
for( int i=0; i<task_count; ++i)
{
start_task([&,i] {
do_work(i);
completion_latch.count_down();
});
}
completion_latch.wait();
33
}
Wait for All Tasks To Start
template<typename F>
void start_task(F&&func);
void prepare_data();
void do_work(int i);
void do_work()
{
latch start_latch(1);
for( int i=0; i<task_count; ++i)
{
start_task([&,i] {
start_latch.wait();
do_work(i);
});
}
prepare_data();
34
start_latch.count_down();
}
Part 3: Atomic Smart Pointers
35
What’s Wrong With Status Quo?
class MyList{
shared_ptr<Node> head;
...
void pop_front(){
std::shared_ptr<Node> p=head;
while(p && !atomic_compare_exchange_strong(&head, &p,
p));
}
};
Error-prone: all access to head must go through atomic_xxx.
Inefficient: atomic_compare_exchange_strong is a free function taking
regular shared_ptr, we don’t want extra synchronization in
shared_ptr! 36
atomic_shared_ptr
class MyList{
atomic_shared_ptr<Node> head;
...
void pop_front(){
std::shared_ptr<Node> p=head;
while(p && !head.compare_exchange_strong(p,p->next));
}
};
atomic_shared_ptr.html
Thanks!
39
backup…
40
future<void> read_async()
{
file_reader reader("myfile.txt");
if (!reader.is_eof()) {
return reader.get_next_string().then([&](auto result){
auto s = result.get();
cout << s << "\n";
if (!reader.is_eof()) {
return reader.get_next_string().then([&](
Just An Idea… auto result) {
auto s = result.get();
cout << s << "\n";
});
}
return make_ready_future();
});
}
return make_ready_future();
}
41
Bug: capturing
future<void> read_async()
{ reader by
file_reader reader("myfile.txt"); reference!
if (!reader.is_eof()) {
return reader.get_next_string().then([&](auto result){
auto s = result.get();
cout << s << "\n";
if (!reader.is_eof()) {
}
return make_ready_future();
});
}
return make_ready_future();
}
42
Read From File, Asynchronously, Fixed By-Ref Issue
future<void> read_async()
{
auto reader = make_shared<file_reader>("myfile.txt");
if (!reader->is_eof()) {
return reader->get_next_string().then([=](auto result) {
auto s = result.get();
cout << s << "\n";
... use reader ...
});
}
return make_ready_future();
}
43
Existing Implementations
Microsoft Visual C++: single global spin-lock
Libc++: 16 global std::mutex-es, access via hashed pointer
44