1. std::async and std::future create background tasks and return values
1.1 std::async
- std::async is a function template used to start an asynchronous task. After starting an asynchronous task, it returns an std::future object. std::future is a class template.
- Start an asynchronous task: create a thread and start executing the corresponding thread entry function. It returns an std::future object. This std::future object contains the result returned by the thread entry function, which is actually the result returned by the thread. You can obtain the result by calling the member function get() of the std::future object.
1.2 std::future
- The object returned by std::async contains the result returned by the thread.
- Future: some people also call std::future. It provides a mechanism to access the results of asynchronous operations, that is, you may not be able to get the results immediately, but you can get the results when the thread is executed, so it can be understood that a value will be saved in this future.
1.3 std::future::get()
- The std::future object uses the get() function to wait for the end of the thread execution and return the result. The get() function will always be stuck waiting for the value. If you don't get the return value, you will never give up.
- The get() function can only be called once, not multiple times. If you get a future object the second time, you will get an exception, mainly because the design of the get function is a mobile semantics, and the bottom layer calls std::move().
1.4 std::future::wait()
- If the std::future object uses the wait() function, it is returned by the waiting thread and does not return the result itself.
- The wait() function can be called multiple times, but the first call to wait() is valid, and the rest are invalid.
1.5 creating asynchronous tasks using normal functions
Example code:
#include <iostream> #include <thread> #include <mutex> #include <future> using namespace std; //Thread entry function int mythread(int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; } int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; //Create a thread and start execution. Bind the future object with the thread created by async. The process is not stuck here std::future<int> result = std::async(mythread); cout << "continue......!" << endl; int def; def = 0; //The program waits for the end of thread execution and returns results through the get() member function of std::future object //This get() function is very good. If you don't get the return value in the future, you will never give up. If you don't get it, you will be stuck here waiting for the value cout << result.get() << endl; //result.wait(); cout << "I love China!" << endl; return 0; }
1.6 creating asynchronous tasks using class member functions
Example code:
#include <iostream> #include <thread> #include <mutex> #include <future> using namespace std; class A { public: //Thread entry function int mythread(int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; } }; int main() { A a; int tmppar = 12; cout << "main " << "threadid = " << std::this_thread::get_id() << endl; //Create a thread and start execution. Bind the future object with the thread created by async. The process is not stuck here //The second parameter is the object reference to ensure that the same object is used in the thread std::future<int> result = std::async(&A::mythread, &a, tmppar); cout << "continue......!" << endl; int def; def = 0; cout << result.get() << endl; //result.wait(); cout << "I love China!" << endl; return 0; }
1.7 std::lanuch
- We achieve some special purposes by passing an additional parameter to std::async(), which is of type std::lanuch (enumeration type).
1.7.1 std::lanuch::deferred
-
std::lanuch::deferred: indicates that the thread entry function call is delayed until the wait() or get() function call of std::future; If wait () or get () is not called, the thread will not be executed. In fact, the thread is not created at all.
#include <iostream> #include <thread> #include <mutex> #include <future> using namespace std; class A { public: //Thread entry function int mythread(int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; } }; int main() { A a; int tmppar = 12; cout << "main " << "threadid = " << std::this_thread::get_id() << endl; //Create a thread and start execution. Bind the future object with the thread created by async. The process is not stuck here //The second parameter is the object reference to ensure that the same object is used in the thread std::future<int> result = std::async(std::launch::deferred,&A::mythread, &a, tmppar); //std::future<int> result = std::async(std::launch::async,&A::mythread, &a, tmppar); cout << "continue......!" << endl; int def; def = 0; cout << result.get() << endl; //result.wait(); cout << "I love China!" << endl; return 0; }
When the get() function is not called, the code execution result is shown in the figure below:
The result shows that if wait() or get() is not called, the thread will not be executed. In fact, the thread is not created at all.
-
When the get() function is called, the code execution result is shown in the figure below:
From first to last, we can see that the child thread ID is the same as the main thread ID, which means that when using async: std:, launch:: deferred, there is no creation of child thread, and there is only one main thread from beginning to end, which is called thread entrance function in main thread.
1.7.2 std::lanuch::async
- std::lanuch::async: indicates that a thread is created when the std::async() function is called, forcing the creation of a thread.
2,std::packaged_task
- std::packaged_task: a class template whose template parameters are various callable objects;
- Through std::packaged_task wraps various callable objects, which can be called as thread entry functions in the future.
std::packaged_ Example code of task wrapper ordinary calling function:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; //Thread entry function int mythread(int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; } int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::packaged_task<int(int)> mypt(mythread); //We pass the function mythread through packaged_ Wrap the task std::thread t1(std::ref(mypt), 1); //The thread starts execution directly, and the second parameter is the parameter of the thread entry function t1.join(); //Wait for the thread to finish executing //The std::future object contains the returned results of the thread entry function. Here, result saves the results returned by the mythread function std::future<int> result = mypt.get_future(); cout << result.get() << endl; cout << "I love China!" << endl; return 0; }
std::packaged_ Example code of task wrapped lambda expression:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::packaged_task<int(int)> mypt([](int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; }); std::thread t1(std::ref(mypt), 1); //The thread starts execution directly, and the second parameter is the parameter of the thread entry function t1.join(); //Wait for the thread to finish executing //The std::future object contains the returned results of the thread entry function. Here, result saves the results returned by the mythread function std::future<int> result = mypt.get_future(); cout << result.get() << endl; cout << "I love China!" << endl; return 0; }
As a result, the child thread and the main thread have different IDS:
-
std::packaged_ The callable object wrapped by task can also be called directly. From this point of view, STD:: packaged_ The task object is also a callable object.
-
If you call STD:: packaged directly_ Task object, which is equivalent to executing a function, will not create a new thread.
Example code:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::packaged_task<int(int)> mypt([](int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; }); mypt(105); //Direct call std::future<int> result = mypt.get_future(); cout << result.get() << endl; cout << "I love China!" << endl; return 0; }
The results are as follows:
-
Using container pair STD:: packaged_ To store and call the task object, use std::move() to move the object, otherwise it will consume resources for replication.
The code is as follows:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; vector<std::packaged_task<int(int)>> mytasks; //container int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::packaged_task<int(int)> mypt([](int mypar) { cout << mypar << endl; //Print new thread id value cout << "mythread() start " << "threadid = " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); //Rest for 5s std::this_thread::sleep_for(dura); cout << "mythread() end " << "threadid = " << std::this_thread::get_id() << endl; return 5; }); mytasks.push_back(std::move(mypt)); //In the container, the mobile semantics is used here. After entering, mypt is empty std::packaged_task<int(int)> mypt2; auto iter = mytasks.begin(); mypt2 = std::move(*iter); //Using mobile semantics mytasks.erase(iter); //Delete the first element. The iteration has expired, so subsequent code can no longer use iter mypt2(123); std::future<int> result = mypt2.get_future(); cout << result.get() << endl; cout << "I love China!" << endl; return 0; }
As a result, no new thread will be created:
3,std::promise
- std::promise: class template.
- Function: can assign a value to it in a thread, and then take this value out for use in other threads.
Example code:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; void mythread(std::promise<int>& tmpp, int calc) //Pay attention to the first parameter { cout << "mythread()id = " << std::this_thread::get_id() << endl; //Here is a series of complex operations calc++; calc *= 10; //Do other calculations, such as five seconds std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); //Finally calculated the result int result = calc; //Save results tmpp.set_value(result); //The results are saved to the tmpp object return; } int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::promise<int> myprom; //Declare an std::promise object myprom, and the saved value type is int std::thread t1(mythread, std::ref(myprom), 180); t1.join(); //Get result value std::future<int> fu1 = myprom.get_future(); //promise and future binding, used to get the return value of the thread auto result = fu1.get(); //get can only be called once, not multiple times cout << "result = " << result << endl; cout << "I love China!" << endl; return 0; }
The results are as follows:
-
Pass the result of one thread to another thread for use
Example code:
#include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> #include <future> using namespace std; void mythread(std::promise<int>& tmpp, int calc) //Pay attention to the first parameter { cout << "mythread()id = " << std::this_thread::get_id() << endl; //Here is a series of complex operations calc++; calc *= 10; //Do other calculations, such as five seconds std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); //Finally calculated the result int result = calc; //Save results tmpp.set_value(result); //The results are saved to the tmpp object return; } void mythread2(std::future<int>& tmpf) { cout << "mythread2()id = " << std::this_thread::get_id() << endl; auto result = tmpf.get(); cout << "mythread2 result = " << result << endl; return; } int main() { cout << "main " << "threadid = " << std::this_thread::get_id() << endl; std::promise<int> myprom; //Declare an std::promise object myprom, and the saved value type is int std::thread t1(mythread, std::ref(myprom), 180); t1.join(); //Get result value std::future<int> fu1 = myprom.get_future(); //promise and future binding, used to get the return value of the thread std::thread t2(mythread2, std::ref(fu1)); t2.join(); //Wait until mythread2 is executed cout << "I love China!" << endl; return 0; }
The results are as follows:
4. Summary
- The purpose of learning these things is not to use them in our own practical development.
- On the contrary, if we can write a stable and efficient multithreaded program with the least things, it is more commendable.
- In order to grow, we must read the code written by some experts, so as to quickly realize the accumulation of our own code, and our technology will be greatly improved.
Note: I study c + + multithreading video address: C + + multithreaded learning address