effective modern c++ - item 35 & 36

35
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++

Upload: chih-hsuan-kuo

Post on 13-Apr-2017

863 views

Category:

Technology


0 download

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++

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 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 :)

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.