effective modern c++ - item 35 & 36
TRANSCRIPT
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] [email protected]
Effective Modern C++
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 Value use 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 Error use 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
callthrow exception
Handle Error with std::threadint 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
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
× 100oversubscription
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 :)
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.
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
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
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
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.