C++11 多线程 —— 同步操作之 future

使用 future 等待一次性事件

简介:The C++ Standard Library models one-off event(一次性事件) with something called a future. If a thread needs to wait for a specific one-off event, it somehow obtains a future representing this event. The thread can then periodically wait on the future for short periods of time to see if the event has occurred while performing some other task in between checks.

两种 future :There are two sorts of futures in the C++ Standard Library, implemented as two class templates declared in the <future> library header: unique futures (std::future<>) and shared futures (std::shared_future<>). These are modeled after std::unique_ptr and std::shared_ptr.

区别An instance of std::future is the one and only instance that refers to its associated event, whereas multiple instances of std::shared_future may refer to the same event. In the latter case, all the instances will become ready at the same time, and they may all access any data associated with the event.

使用模板参数指定数据类型The template parameter is the type of the associated data. The std:future<void>, std::shared_future<void> template specializations should be used where there’s no associated data.

future本身没有提供同步:Although futures are used to communicate between threads, the future objects themselves don’t provide synchronized accesses. If multiple threads need to access a single future object, they must protect access via a mutex or other synchronization mechanism. However, as you’ll see, multiple threads may each access their own copy of a std::shared_future<> without further synchronization, even if they all refer to the same asynchronous result.


1. 从后台任务返回值

怎么做?)You use std::async to start an asynchronous task for which you don’t need the result right away. Rather than giving you back a std::thread object to wait on, std::async returns a std::future object, which will eventually hold the return value of the function. When you need the value, you just call get() on the future, and the thread blocks until the future is ready and then returns the value. 如,

#include <future>
#include <iostream>

int find_the_answer_to_ltuae();
void do_other_stuff();

int main() {
	std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
	
	do_other_stuff();
	
	std::cout<<"The answer is "<< the_answer.get() <<std::endl;			// 获取返回值
}

std::async()允许传入参数std::async allows you to pass additional arguments to the function by adding extra arguments to the call, in the same way that std::thread does.
If the first argument is a pointer to a member function, the second argument provides the object on which to apply the member function (either directly, or via a pointer, or wrapped in std::ref), and the remaining arguments are passed as arguments to the member function. Otherwise, the second and subsequent arguments are passed as arguments to the function or callable object specified as the first argument. Just as with std::thread, if the arguments are rvalues, the copies are created by moving the originals. 如,

struct X {
	void foo(int,std::string const&);
	std::string bar(std::string const&);
};

X x;
auto f1=std::async(&X::foo,&x,42,"hello");		// Calls p->foo(42,"hello") where p is &x
auto f2=std::async(&X::bar,x,"goodbye");		// Calls tmpx.bar("goodbye") where tmpx is a copy of x

struct Y {
	double operator()(double);
};
Y y;
auto f3=std::async(Y(),3.141);			// Calls tmpy(3.141) where tmpy is move-constructed from Y()
auto f4=std::async(std::ref(y),2.718);	// Calls y(2.718)

指定 std::async 如何执行)By default, it’s up to the implementation whether std::async starts a new thread, or whether the task runs synchronously when the future is waited for. In most cases this is what you want, but you can specify which to use with an additional parameter to std::async before the function to call. This parameter is of the type std::launch, and can either be std::launch::deferred to indicate that the function call is to be deferred until either wait() or get() is called on the future, std::launch::async to indicate that the function must be run on its own thread, or std::launch::deferred | std::launch::async to indicate that the implementation may choose. This last option is the default. If the function call is deferred, it may never actually run. 如,

auto f6=std::async(std::launch::async, Y(), 1.2);		// Run in new thread

auto f7=std::async(std::launch::deferred, baz,std::ref(x));	// Run in wait() or get()

auto f8=std::async(std::launch::deferred | std::launch::async, baz, std::ref(x));	// Implementation choose

auto f9=std::async(baz, std::ref(x));		// Implementation choose

f7.wait();		// Invoke deferred function

2. 将任务和 future 关联

packaged_task简介std::packaged_task<> ties a future to a function or callable object. When the std::packaged_task<> object is invoked, it calls the associated function or callable object and makes the future ready, with the return value stored as the associated data.

通过模板参数指定函数签名The template parameter for the std::packaged_task<> class template is a function signature, like void() for a function taking no parameters with no return value, or int(std::string&,double*) for a function that takes a non-const reference to a std::string and a pointer to a double and returns an int.

获取关联的 future)The return type of the specified function signature identifies the type of the std::future<> returned from the get_future() member function.

扫描二维码关注公众号,回复: 5523659 查看本文章

packaged_task对象是可调用对象)The std::packaged_task object is a callable object, and it can be wrapped in a std::function object, passed to a std::thread as the thread function, passed to another function that requires a callable object, or even invoked directly.
When the std::packaged_task is invoked as a function object, the arguments supplied to the function call operator are passed on to the contained function, and the return value is stored as the asynchronous result in the std::future obtained from get_future().

例子:

std::mutex m;
std::deque<std::packaged_task<void()> > tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();

void gui_thread() {
	while(!gui_shutdown_message_received()) {
		get_and_process_gui_message();
		
		// 从任务队列中弹出一个任务,然后执行该任务
		std::packaged_task<void()> task;
		{
			std::lock_guard<std::mutex> lk(m);
			
			if(tasks.empty())
				continue;
				
			task=std::move(tasks.front());
			tasks.pop_front();
		}
		task();		// 执行任务
	}
}

std::thread gui_bg_thread(gui_thread);

// 将任务推人任务队列,然后返回与该任务关联的 future
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f) {
	std::packaged_task<void()> task(f);
	std::future<void> res=task.get_future();
	
	std::lock_guard<std::mutex> lk(m);
	tasks.push_back(std::move(task));
	
	return res;
}

3. std::promise/std::future 对

std::promise写,std::future读std::promise<T> provides a means of setting a value (of type T), which can later be read through an associated std::future<T> object.

同步机制)The waiting thread could block on the future, while the thread providing the data could use the promise half of the pairing to set the associated value and make the future ready.

获取关联的 future)You can obtain the std::future object associated with a given std::promise by calling the get_future() member function, just like with std::packaged_task.

写入数据)When the value of the promise is set (using the set_value() member function), the future becomes ready and can be used to retrieve the stored value. If you destroy the std::promise without setting a value, an exception is stored instead.

例子:

void print_int(std::future<int>& fut) {
    int x = fut.get(); 						// 获取共享状态的值.
    std::cout << "value: " << x << '\n'; 	// 打印 value: 10.
}

int main () {
    std::promise<int> prom; 					// 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future(); 	// 获取关联的 future
    
    std::thread t(print_int, std::ref(fut)); 	// 将 future 交给另外一个线程 t.
    
    prom.set_value(10); 						// 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}

4. 在 future 中保存异常

std::async中)If the function call invoked as part of std::async throws an exception, that exception is stored in the future in place of a stored value, the future becomes ready, and a call to get() rethrows that stored exception.

std::packaged_task中)The same happens if you wrap the function in a std::packaged_task—when the task is invoked, if the wrapped function throws an exception, that exception is stored in the future in place of the result, ready to be thrown on a call to get().

std::promise中)Naturally, std::promise provides the same facility, with an explicit function call. If you wish to store an exception rather than a value, you call the set_exception()
member function rather than set_value(). This would typically be used in a catch block for an exception thrown as part of the algorithm, to populate the promise with that exception. 如,

extern std::promise<double> some_promise;

try {
	some_promise.set_value(calculate_value());
}
catch(...) {
	some_promise.set_exception(std::current_exception());	// 保存异常
}

This uses std::current_exception() to retrieve the thrown exception; the alternative here would be to use std::copy_exception() to store a new exception directly without throwing:

some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));

This is much cleaner than using a try/catch block if the type of the exception is known, and it should be used in preference; not only does it simplify the code, but it also provides the compiler with greater opportunity to optimize the code.
(使用第一种方式时,你必须在try语句中抛出异常,然后在catch语句中使std::current_exception()函数来获取当前抛出的异常;使用第二种方式时,则无须先抛出异常,当然前提是你知道异常的类型。)

其他方式:“言而无信”)Another way to store an exception in a future is to destroy the std::promise or std::packaged_task associated with the future without calling either of the set functions on the promise or invoking the packaged task. In either case, the destructor of the std::promise or std::packaged_task will store a std::future_error exception with an error code of std::future_errc::broken_promise in the associated state if the future isn’t already ready.


5. 在多个线程中等待

std::shared_future对象可复制)Multiple threads can wait for the same event. Whereas std::future is only moveable, so ownership can be transferred between instances, but only one instance refers to a particular asynchronous result at a time, std::shared_future instances are copyable, so you can have multiple objects referring to the same associated state.

每个线程有自己的副本,所以是线程安全的)Accesses to the shared asynchronous state from multiple threads are safe if each thread accesses that state through its own std::shared_future object.

创建方式)Instances of std::shared_future that reference some asynchronous state are constructed from instances of std::future that reference that state. The ownership must be transferred into the std::shared_future using std::move, leaving the std::future in an empty state, as if it was default constructed. 如,

std::promise<int> p;
std::future<int> f(p.get_future());
std::shared_future<int> sf(std::move(f));

std::future has a share() member function that creates a new std::shared_future and transfers ownership to it directly.

std::promise<int> p;
auto sf=p.get_future().share();

猜你喜欢

转载自blog.csdn.net/fcku_88/article/details/88366098