C++11 concurrency and multithreading notes async, future, packaged_task,promise

Keywords: C++ Multithreading

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

Posted by DoddsAntS on Sat, 06 Nov 2021 01:54:01 -0700