Concurrency TS - Editor's Report - Artur Laksberg - CppCon 2015

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 44

Concurrency TS

Editor’s Report
Artur Laksberg
[email protected]
Microsoft Corp.
2015
Technical Specification for C++ Extensions for Concurrency

 Produced by the Concurrency Study Group (SG1) with input from


LEWG, LWG
 Separate document and is not part of ISO C++ Standard
 Goal: Eventual Inclusion into ISO C++ Standard
 Available online: http://wg21.link/n4538
 Work in progress: https://github.com/cplusplus/concurrency_ts

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();

future<int> len = f.then([](future<string> result) {


string s = result.get();
int n = s.length();
return n;
});
}

7
Implicit Unwrapping
future<string> download_html(string url);

void process()
{
future<string> f = read_string_from_file();

future<string> = f.then([](future<string> result) {


string url = result.get();
future<string> s = download_html(url);
return s;
});
}

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 = ...;

auto ready = when_all(fi, 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>>>>

auto ready = when_any(futures.begin(), futures.end());

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 = ...;

auto ready = when_any(fi, 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");

auto predicate = [=] { return reader->is_eof(); };


auto iter = [=] { return reader->get_next_string(); };
auto func = [=] (auto result) {
auto s = result.get();
cout << s << "\n";
};

return make_iterative_future(predicate, iter, func);


}

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");

auto predicate = [=] { return reader->is_eof(); };


auto iter = [=] { return reader->get_next_string(); };
auto func = [=] (shared_future<string> result) {
auto s = result.get();
cout << s << "\n";
};

return make_iterative_future(predicate, iter, func);


}

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();

void waiting() void detecting()


{ {
cout << "Waiting...\n"; if (is_condition_satisfied())
unique_lock<mutex> lk(m); {
cv.wait(lk); cv.notify_one();
cout << "Notification received\n"; }
} }

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;

void waiting() void detecting()


{ {
cout << "Waiting...\n"; if (is_condition_satisfied())
unique_lock<mutex> lk(m); {
while(!flag) { {
cv.wait(lk); lock_guard<mutex> g(m);
} flag = true;
cout << "Received notification\n"; }
} cv.notify_one();
} 28
}
Signal an Event (Corrected)
condition_variable cv;
mutex m;
bool flag = false;

void waiting() void detecting()


{ {
cout << "Waiting...\n"; if (is_condition_satisfied())
unique_lock<mutex> lk(m); {
{
cv.wait(lk, [] { return flag; }); lock_guard<mutex> g(m);
flag = true;
cout << "Received notification\n"; }
} cv.notify_one();
} 29
}
Signaling With std::future
promise<void> p;

bool is_condition_satisfied();

void waiting() void detecting()


{ {
cout << "Waiting...\n"; if (is_condition_satisfied())
p.get_future().wait(); {
cout << "Received notification\n"; p.set_value();
} }
}

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();

void waiting() void detecting()


{ {
cout << "Waiting...\n"; if (is_condition_satisfied())
l.wait(); {
cout << "Received notification\n"; l.count_down();
} }
}

32
Wait for All Tasks To Finish
template<typename F>
void start_task(F&&func);

void do_work(int i);

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));
}
};

 Guaranteed atomic access


 Can be implemented more efficiently
37
References
 Concurrency TS: http://wg21.link/n4538
 “Improvements to std::future and Related APIs”, Niklas Gustafsson,
Artur Laksberg, Herb Sutter, Sana Mithani: http://wg21.link/n3857
 “C++ Latches and Barriers”, Alasdair Mackintosh, Olivier Giroux:
http://wg21.link/n4204
 “Atomic Smart Pointers, rev. 1”, Herb Sutter: http://
wg21.link/n4162
 “Why do we need atomic_shared_ptr?”, Anthony Williams:
https://www.justsoftwaresolutions.co.uk/threading/why-do-we-need- 38

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()) {

But! We have a return reader.get_next_string().then([&](


auto result) {
auto s = result.get();
problem! });
cout << s << "\n";

}
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

You might also like