Asynchronous functions are functions which can run in the background, and we can proceed ahead with the rest of the program. The timing of such tasks cannot / need-not be determined.
There are two ways of thinking of async functionality -
1) In terms of thread
2) In terms of task.
You'll notice that it is much more natural and easier to think of tasks. We work on our tasks, both personal and professional on a daily basis. Whereas thread is an abstract concept, mainly associated with computers and programming.
If we use thread based approach in C++, we need to use std::thread.
std::thread ( myFunc, parameters.... );
If we use the task based approach, we can use std::async
future<returnTypeOfMyFunc> futureVar = std::async(myFunc, parameters.... );
Another evident advantage of using the task based approach i.e std::async is that, it is quite easy to collect the return value using futureVar.get(). On the other hand there is no easy way to get the return value in the thread based approach.
There is another compelling reason why we should use std::async. The async API abstracts away all the details of thread creation, thread scheduling, so that we can only concentrate on the logic of the program.
We cannot create more threads than the number of software / OS threads. If we try to create more threads than the max limit, std::system_error is thrown. Thus creating threads is quite risky. Keeping tab on the number of threads, and creating threads with proper planning, might make the code efficient in your system but inefficient in other systems.
Even if we don't run out of software threads, we might end up with a large number of software threads compared to hardware threads. This will require the scheduler to divide the time between different threads. Switching threads wastes a lot of time, as we need to save the data, registers and cache of the current threads, and reload the same for the new thread. Managing over-subscription of threads is not that straightforward, since our solutions should ideally work on all machines in the most efficient manner.
std::async takes away all these problems. All the issues with thread management is now taken care by the standard library. This happens because when std::async is called without specifying a launch policy, and a default launch policy is used. std::async then, in runtime intelligently checks the current state to verify if the threads are over-subscribed or not. If the threads are NOT over-subscribed, then a new thread is created. Otherwise the async function is run in the current thread.
However, there are scenarios where we might have a strict requirement that a new thread be created. For example, a user sets a few parameters in the GUI and clicks compute. The GUI thread must create a new task to run this heavy, time-consuming computation, otherwise the GUI will become unresponsive. In such scenarios, we should call std::async with the launch policy std::launch::async. This is guaranteed to create a new thread ( unless we have reached the maximum number of threads in which case std::system_error is thrown).
However threads are still useful where -
To use std::async, include <future> header.
To use std::thread, include <thread> header.
Below I will demonstrate the above theory with a few examples.
When I run the above program, the exception is not caught, but the program exits with the error
In my PC, the breaking point is when the number of threads is equal 1886. On ideone.com, the exception "Resource temporarily unavailable" is thrown when NUM_OF_THREADS exceeds 15.
Now let's try the same with std::async.
I have replaced the creation of new threads with creation of async tasks. Notice the associated changes on line 4 and 24. This code runs comfortably even with NUM_OF_TASKS=10000.
Thus we should always thrive to use std::async, instead of std::thread (except in a few situations mentioned above) .
This article is inspired by Scott Meyer's Effective Modern C++. I highly recommend you guys to check it out.
There are two ways of thinking of async functionality -
1) In terms of thread
2) In terms of task.
You'll notice that it is much more natural and easier to think of tasks. We work on our tasks, both personal and professional on a daily basis. Whereas thread is an abstract concept, mainly associated with computers and programming.
If we use thread based approach in C++, we need to use std::thread.
std::thread ( myFunc, parameters.... );
If we use the task based approach, we can use std::async
future<returnTypeOfMyFunc> futureVar = std::async(myFunc, parameters.... );
Another evident advantage of using the task based approach i.e std::async is that, it is quite easy to collect the return value using futureVar.get(). On the other hand there is no easy way to get the return value in the thread based approach.
There is another compelling reason why we should use std::async. The async API abstracts away all the details of thread creation, thread scheduling, so that we can only concentrate on the logic of the program.
We cannot create more threads than the number of software / OS threads. If we try to create more threads than the max limit, std::system_error is thrown. Thus creating threads is quite risky. Keeping tab on the number of threads, and creating threads with proper planning, might make the code efficient in your system but inefficient in other systems.
Even if we don't run out of software threads, we might end up with a large number of software threads compared to hardware threads. This will require the scheduler to divide the time between different threads. Switching threads wastes a lot of time, as we need to save the data, registers and cache of the current threads, and reload the same for the new thread. Managing over-subscription of threads is not that straightforward, since our solutions should ideally work on all machines in the most efficient manner.
std::async takes away all these problems. All the issues with thread management is now taken care by the standard library. This happens because when std::async is called without specifying a launch policy, and a default launch policy is used. std::async then, in runtime intelligently checks the current state to verify if the threads are over-subscribed or not. If the threads are NOT over-subscribed, then a new thread is created. Otherwise the async function is run in the current thread.
However, there are scenarios where we might have a strict requirement that a new thread be created. For example, a user sets a few parameters in the GUI and clicks compute. The GUI thread must create a new task to run this heavy, time-consuming computation, otherwise the GUI will become unresponsive. In such scenarios, we should call std::async with the launch policy std::launch::async. This is guaranteed to create a new thread ( unless we have reached the maximum number of threads in which case std::system_error is thrown).
However threads are still useful where -
- We have known program performance, thread usage profile and hardware characteristics.
- Creating thread pools.
To use std::async, include <future> header.
To use std::thread, include <thread> header.
Below I will demonstrate the above theory with a few examples.
When I run the above program, the exception is not caught, but the program exits with the error
In my PC, the breaking point is when the number of threads is equal 1886. On ideone.com, the exception "Resource temporarily unavailable" is thrown when NUM_OF_THREADS exceeds 15.
Now let's try the same with std::async.
I have replaced the creation of new threads with creation of async tasks. Notice the associated changes on line 4 and 24. This code runs comfortably even with NUM_OF_TASKS=10000.
Thus we should always thrive to use std::async, instead of std::thread (except in a few situations mentioned above) .
This article is inspired by Scott Meyer's Effective Modern C++. I highly recommend you guys to check it out.
Modern Effective C++ - Prefer using task based approach in multithreaded programs
Reviewed by zeroingTheDot
on
May 05, 2018
Rating:
No comments: