Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
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++
Item 35:
Prefer task-based programming
to thread-based.
Concurrency in C++11
int doAsyncWork();
// thread-based programming
std::thread t(doAsyncWork);
// task-based programming
auto fut = std::async(doAsyncWork);
std::future<int>
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
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
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
Program terminated! CC 2.0
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
Nightmare with Thread Management
(oversubscription)
CPU provides
2 threads
system has
100 threads
ready to run
× 100
oversubscription
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
– Effective Modern C++, Scott Meyer
“Your life will be easier if you dump these problems
on somebody else, and using std::async.”
std::async takes responsibility for thread management.
#1, running task 1
#2, running task 2
#3, empty
task 3
task 4
task 5
task 6
task 7
task queue
Discuss in item 36 later :)
BUT
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.
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.
Item 36:
Specify std::launch::async if
asynchronicity is essential.
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.
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
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.
- 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.
std::async(task)
async policy
deferred policy
looks good!
thread_local variables
timeout-based wait
affect
With deferred policy,
thread_local variables will act as
normal global variables.
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;
}
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
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;
}
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
With deferred policy, wait_for
and wait_until will return
std::future_status::deferred.
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
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
BUT
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.
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)…);
}
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)…);
}
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.
Thanks.
CC-BY-SA

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++
  • 2. Item 35: Prefer task-based programming to thread-based.
  • 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.”
  • 12. std::async takes responsibility for thread management. #1, running task 1 #2, running task 2 #3, empty task 3 task 4 task 5 task 6 task 7 task queue Discuss in item 36 later :)
  • 13. BUT
  • 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.
  • 16. Item 36: Specify std::launch::async if asynchronicity is essential.
  • 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.
  • 21. std::async(task) async policy deferred policy looks good! thread_local variables timeout-based wait affect
  • 22. With deferred policy, thread_local variables will act as normal global variables.
  • 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
  • 30. BUT
  • 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.