The document discusses concurrency in C++ and the use of std::async and std::future. It recommends preferring task-based programming over thread-based due to easier management. It notes that the default launch policy for std::async allows asynchronous or synchronous execution, creating uncertainty. It advises specifying std::launch::async if asynchronicity is essential to ensure concurrent execution and avoid issues with thread-local variables and timeout-based waits.
1 of 35
More Related Content
Effective Modern C++ - Item 35 & 36
1. Item 35:
Prefer task-based programming to
thread-based.
Item 36:
Specify std::launch::async if
asynchronicity is essential.
BE RATIONAL, NOT SOUR.
Tommy Kuo [:KuoE0]
kuoe0@mozilla.com
Effective Modern C++
3. Concurrency in C++11
int doAsyncWork();
// thread-based programming
std::thread t(doAsyncWork);
// task-based programming
auto fut = std::async(doAsyncWork);
std::future<int>
4. Return Valueuse get() to get the return value
std::async
function
std::future
object
return
int fib(int x) {
return x <= 2 ? x - 1 : fib(x - 1) + fib(x -
2);
}
int main() {
auto fut = std::async(fib, 40);
auto ret = fut.get();
std::cout << ret << std::endl;
return 0;
}
std::future::get
function
call
5. Handle Erroruse get() to throw exceptions
std::async
function
std::future
object
return
int fib(int x) {
if (x < 1) throw logic_error("Don't you know
Fibonacci?");
return x <= 2 ? x - 1 : fib(x - 1) + fib(x - 2);
}
int main() {
auto fut = async(fib, 0);
try {
cout << fut.get() << endl;
} catch(exception& e) {
cout << e.what() << endl;
}
return 0;
}
std::future::get
function
call
throw exception
6. Handle Error with std::thread
int fib(int x) {
if (x < 1) throw logic_error("Don't you know Fibonacci?");
return x <= 2 ? x - 1 : fib(x - 1) + fib(x - 2);
}
int main() {
try {
auto t = thread(fib, 0);
} catch(exception& e) {
cout << e.what() << endl;
}
return 0;
}
throw exception
8. Nightmare with Thread Management
(thread exhaustion)
system provides
6 threads.
int main() {
std::vector<std::thread> thread_pool(1000);
return 0;
}
throw std::system_error exception
more than system can provide
9. Nightmare with Thread Management
(oversubscription)
CPU provides
2 threads
system has
100 threads
ready to run
× 100
oversubscription
10. Nightmare with Thread Management
(oversubscription)
CPU provides
2 threads
system has
100 threads
ready to run
× 100
Context switches increase the
overall thread management
overhead of the system.
oversubscription
11. – Effective Modern C++, Scott Meyer
“Your life will be easier if you dump these problems
on somebody else, and using std::async.”
14. When to Use std::thread
- Need access to the API of the underlying threading
implementation.
Using std::thread::native_handle to get the lower-level platform-
specific thread (pthreads or Windows’ Threads).
- Need to and are able to optimize thread usage.
- Need to implement threading technology beyond the C++
concurrency API.
15. Things to Remember
- The std::thread API offers no direct way to get return values from
asynchronously run functions, and if those functions throw, the program is
terminated.
- Thread-based programming calls for manual management of thread
exhaustion, oversubscription, load balancing, and adaptation to new
platforms.
- Task-based programming via std::async with the default launch policy
handles most of these issues for you.
17. Launch Policy of std::async
- std::launch::async
Task must be run asynchronously.
- std::launch::deferred
Task may run only when get or wait is called on the future
returned by std::async.
18. You have to present
on 2016/01/06.
2015/10/01 2015/10/01
Junior
Done. 😎
2015/10/02 2016/01/05
I hope I can finish it
before Wednesday. 😭
You have to present
on 2016/01/06.
Junior
Tommy (with async-driven) Tommy (with deferred-driven)
behavior of async policy behavior of deferred policy
OK! 😎
Tommy (with async-driven)
OK! 😎
Tommy (with async-driven)
Are you ready?
Junior
Are you ready?
Junior
19. Default Launch Policy
auto fut = std::async(task);
// create with default launch policy
auto fut = std::async(std::launch::async |
std::launch::deferred, task);
// as same as the default launch policy
The default policy thus permits tasks to be run either
asynchronously or synchronously.
20. - Not possible to predict whether tasks will run concurrently.
- Not possible to predict whether tasks run on a thread
different from the thread invoking get or wait.
- May not be possible to predict whether tasks run at all.
23. thread_local variables with async policy
thread_local int x = 0;
int func(bool update, int val){
return update ? x = val : x;
}
int main(){
x = 9;
std::future<int> task[4];
for (int i = 0; i < 3; ++i) task[i] =
std::async(std::launch::async, func, true, i + 1);
task[3] = std::async(std::launch::async, func, false, 0);
for (auto& t: task) std::cout << t.get();
std::cout << x << std::endl;
return 0;
}
24. thread_local variables with async policy
thread_local int x = 0;
int func(bool update, int val){
return update ? x = val : x;
}
int main(){
x = 9;
std::future<int> task[4];
for (int i = 0; i < 3; ++i) task[i] =
std::async(std::launch::async, func, true, i + 1);
task[3] = std::async(std::launch::async, func, false, 0);
for (auto& t: task) std::cout << t.get();
std::cout << x << std::endl;
return 0;
}
output
——————————————————————
12309
25. thread_local variables with deferred policy
thread_local int x = 0;
int func(bool update, int val){
return update ? x = val : x;
}
int main(){
x = 9;
std::future<int> task[4];
for (int i = 0; i < 3; ++i) task[i] =
std::async(std::launch::deferred, func, true, i + 1);
task[3] = std::async(std::launch::deferred, func, false, 0);
for (auto& t: task) std::cout << t.get();
std::cout << x << std::endl;
return 0;
}
26. thread_local variables with deferred policy
thread_local int x = 0;
int func(bool update, int val){
return update ? x = val : x;
}
int main(){
x = 9;
std::future<int> task[4];
for (int i = 0; i < 3; ++i) task[i] =
std::async(std::launch::deferred, func, true, i + 1);
task[3] = std::async(std::launch::deferred, func, false, 0);
for (auto& t: task) std::cout << t.get();
std::cout << x << std::endl;
return 0;
}
output
——————————————————————
12333
27. With deferred policy, wait_for
and wait_until will return
std::future_status::deferred.
28. Infinite-loop problem with timeout-based wait
int main() {
auto fut = std::async(std::launch::deferred,
task); // deferred policy
while (fut.wait_for(100ms) !=
std::future_status::ready) {
std::cout << “waiting…” << std::endl;
}
std::cout << fut.get() << std::endl;
return 0;
}
return std::future_status::deferred always
29. Resolve infinite-loop problem
int main() {
auto fut = std::async(task); // deferred policy
if (fut.wait_for(0ms) ==
std::future_status::deferred) {
fut.wait(); // fut.get() also works.
} else {
while (fut.wait_for(100ms) !=
std::future_status::ready) {
std::cout << “waiting…” << std::endl;
}
}
std::cout << fut.get() << std::endl;
return 0;
}
check it and make it
run synchronously
31. When to Use Default Launch Policy
- The task need not run concurrently with the thread calling get or wait.
- It doesn’t matter which thread’s thread_local variables are read or written.
- Either there’s a guarantee that get or wait will be called on the future
returned by std::async.
- It’s acceptable that the task may never execute.
- Code using wait_for or wait_until takes the possibility of deferred status
into account.
32. Function to Use Async Policy (C++11)
template<typename F, typename... Ts>
inline
std::future<typename std::result_of<F(Ts…)>::type>
reallyAsync(F&& f, Ts&&... params)
{
return std::async(std::launch::async,
std::forward<F>(f),
std::forward<Ts>(params)…);
}
33. Function to Use Async Policy (C++14)
template<typename F, typename... Ts>
inline
auto
reallyAsync(F&& f, Ts&&... params)
{
return std::async(std::launch::async,
std::forward<F>(f),
std::forward<Ts>(params)…);
}
34. Things to Remember
- The default launch policy for std::async permits both
asynchronous and synchronous task execution.
- This flexibility leads to uncertainty when accessing thread_local
variables, implies that the task may never execute, and affects
program logic for timeout-based wait calls.
- Specify std::launch::async if asynchronous task execution is
essential.