C++2.0——多线程的使用 promise 和 packaged_task

发布时间:2023-07-01 09:30

头文件中包含了以下几个类和函数:

promise

(C++11)

future

(C++11)

shared_future

(C++11)

packaged_task

(C++11)

async

(C++11)

launch

(C++11)

future_status

(C++11)

future_error

(C++11)

future_category

(C++11)

future_errc

(C++11)

std::promise

定义于头文件 

   

template< class R > class promise;

空模板 (C++11 起)

template< class R > class promise;

非 void 特化,用于在线程间交流对象 (C++11 起)

template<>          class promise;

void 特化,用于交流无状态事件 (C++11 起)

类模板 std::promise 提供存储值或异常的设施,之后通过 std::promise 对象所创建的 std::future 对象异步获得结果。注意 std::promise 只应当使用一次。

每个 promise 与共享状态关联,共享状态含有一些状态信息和可能仍未求值的结果,它求值为值(可能为 void )或求值为异常。 promise 可以对共享状态做三件事:

  • 使就绪: promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。
  • 释放: promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。
  • 抛弃: promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它。

promise 是 promise-future 交流通道的“推”端:存储值于共享状态的操作同步于(定义于 std::memory_order )任何在共享状态上等待的函数(如 std::future::get )的成功返回。其他情况下对共享状态的共时访问可能冲突:例如, std::shared_future::get 的多个调用方必须全都是只读,或提供外部同步。

std::promise 的构造函数

default (1)
promise();
默认构造函数,初始化一个空的共享状态
with allocator (2)
template  promise (allocator_arg_t aa, const Alloc& alloc);
带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态
copy [deleted] (3)
promise (const promise&) = delete;
拷贝构造函数,被禁用
move (4)
promise (promise&& x) noexcept;
移动构造函数

简单的使用

#include 
#include 
#include 
#include 
#include 
#include 

void accumulate(std::vector::iterator first,
	std::vector::iterator last,
	std::promise accumulate_promise)
{
	int sum = std::accumulate(first, last, 0);
	accumulate_promise.set_value(sum);  // 提醒 future
}

void do_work(std::promise barrier)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	barrier.set_value();
}

int main()
{
	// 演示用 promise 在线程间传递结果。
	std::vector numbers = { 1, 2, 3, 4, 5, 6 };
	std::promise accumulate_promise;
	std::future accumulate_future = accumulate_promise.get_future();
	std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
		std::move(accumulate_promise));

	// future::get() 将等待直至该 future 拥有合法结果并取得它
	// 无需在 get() 前调用 wait()
	//accumulate_future.wait();  // 等待结果
	std::cout << \"result=\" << accumulate_future.get() << \'\\n\';

	std::cout << \"我应该在result之后 \" << std::endl;

	work_thread.join();  // wait for thread completion

	// 演示用 promise 在线程间对状态发信号
	std::promise barrier;//使用void特化版本
	std::future barrier_future = barrier.get_future();
	std::thread new_work_thread(do_work, std::move(barrier));
	barrier_future.wait();
	new_work_thread.join();


	return system(\"pause\");
}

std::packaged_task

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

 

default (1)
packaged_task() noexcept;

默认构造函数,初始化一个空的共享状态,并且该 packaged_task 对象无包装任务

initialization (2)
template 
  explicit packaged_task (Fn&& fn);
初始化一个共享状态,并且被包装任务由参数 fn 指定
with allocator (3)
template 
  explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态
copy [deleted] (4)
packaged_task (const packaged_task&) = delete;
拷贝构造函数,被禁用
move (5)
packaged_task (packaged_task&& x) noexcept;
移动构造函数

简单的测试

#include 
#include 
using namespace std;

int add(int x, int y) 
{
	cout << \"add(x,y)\" << \" thread_id: \"<< this_thread::get_id() << endl;
	return x + y;
}

int f(int x, int y) 
{
	cout << \"f(x,y)\" << \" thread_id: \" << this_thread::get_id() << endl;
	return std::pow(x,y);
}

void task_thread() 
{
	packaged_task task(add);
	future fu = task.get_future();

	thread task_td(std::move(task),20,34);
	task_td.join();

	cout << \"res= \" << fu.get() << \" task_thread thread_id: \" << this_thread::get_id() << endl;

}

void task_lambda() 
{
	packaged_task task([](int a, int b) { return a + b; });
	future res = task.get_future();
	task(2,9);

	cout << \"task_lambda \" << this_thread::get_id() << endl;
}

void task_bind() 
{
	packaged_task task(bind(f,4,5));//C++11 bind的用法
	future res = task.get_future();

	task();

	cout << \" task_bind \" << this_thread::get_id() << endl;

}

/*
* 类模板 std::packaged_task 包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),
* 使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。
*/
int main() 
{
	task_thread();

	task_lambda();

	task_bind();

	cout << \"main thread id: \" << this_thread::get_id() << endl;

	return system(\"pause\");
}

输出结果:

\"C++2.0——多线程的使用

成员函数

valid

检查任务对象是否拥有合法函数
(公开成员函数)

swap

交换二个任务对象
(公开成员函数)

获取结果

get_future

返回与承诺的结果关联的 std::future
(公开成员函数)

执行

operator()

执行函数
(公开成员函数)

make_ready_at_thread_exit

执行函数,并确保结果仅在一旦当前线程退出时就绪
(公开成员函数)

reset

重置状态,抛弃任何先前执行的存储结果
(公开成员函数)

1. std::packaged_task::valid  和 std::packaged_task::get_future的使用

valid 检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操作或者 swap 操作。

get_future返回一个与 packaged_task 对象共享状态相关的 future 对象。返回的 future 对象可以获得由另外一个线程在该 packaged_task 对象的共享状态上设置的某个值或者异常。

void launcher(packaged_task& task, int x)
{
	if (task.valid()) //检查任务对象是否拥有合法函数
	{
		future res = task.get_future();
		thread t1(std::move(task),x);
		t1.join();

		cout << \" launcher res: \" << res.get() << endl;
	}
}

2. std::packaged_task::operator()(Args... args) 的使用

调用该 packaged_task 对象所包装的对象(通常为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数一般会发生两种情况:

  • 如果成功调用 packaged_task 所包装的对象,则返回值(如果被包装的对象有返回值的话)被保存在 packaged_task 的共享状态中。
  • 如果调用 packaged_task 所包装的对象失败,并且抛出了异常,则异常也会被保存在 packaged_task 的共享状态中。

以上两种情况都使共享状态的标志变为 ready,因此其他等待该共享状态的线程可以获取共享状态的值或者异常并继续执行下去;共享状态的值可以通过在 future 对象(由 get_future获得)上调用 get 来获得。由于被包装的任务在 packaged_task 构造时指定,因此调用 operator() 的效果由 packaged_task 对象构造时所指定的可调用对象来决定:

  • 如果被包装的任务是函数指针或者函数对象,调用 std::packaged_task::operator() 只是将参数传递给被包装的对象。
  • 如果被包装的任务是指向类的非静态成员函数的指针,那么 std::packaged_task::operator() 的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数作为该成员函数的参数。
  • 如果被包装的任务是指向类的非静态成员变量,那么 std::packaged_task::operator() 只允许单个参数。
void task_lambda() 
{
	packaged_task task([](int a, int b) { return a + b; });
	future res = task.get_future();
	task(2,9);

	cout << \"task_lambda \" << this_thread::get_id() << endl;
}

3. make_ready_at_thread_exit的使用

该函数会调用被包装的任务,并向任务传递参数,类似 std::packaged_task 的 operator() 成员函数。但是与 operator() 函数不同的是,make_ready_at_thread_exit 并不会立即设置共享状态的标志为 ready,而是在线程退出时设置共享状态的标志。如果与该 packaged_task 共享状态相关联的 future 对象在 future::get 处等待,则当前的 future::get 调用会被阻塞,直到线程退出。而一旦线程退出,future::get 调用继续执行,或者抛出异常。

4. std::packaged_task::reset

重置 packaged_task 的共享状态,但是保留之前的被包装的任务

int print(int x) 
{
	return x*x;
}

int main()
{
	
	packaged_task task(print);
	
	future result = task.get_future();

	task(100);

	cout << \"task reslut: \" << result.get() << endl;

	task.reset();
	result = task.get_future();

	thread t2(std::move(task),200);
	t2.join();
	
	cout << \"t1 reslut: \" << result.get() << endl;

	return system(\"pause\");

}

 

以上参考:

  • https://www.cnblogs.com/chenny7/p/11996237.html
  • https://www.cnblogs.com/haippy/p/3280643.html
  • https://zh.cppreference.com/w/cpp/thread

 

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号