[C++11 并发编程] 14 关联任务与期望

std::packaged_task<>将期望绑定到一个函数或者可调用对象。当std::packaged_task<>对象被触发时,它将调用关联的函数和可调用对象使得期望被满足,并将返回值填入期望关联的数据之中。这个可以用于构建线程池,也可以用于任务管理(每个任务在各自的线程中执行或所有任务顺序的在一个后台线程中执行)。如果一个大的操作可以被拆分为多个子任务,每个子任务就可以被放入一个std::packaged_task<>实例之中,再将这个实例交给任务调度器或者线程池。这样调度器就可以只与std::packaged_task<>实例打交道而不是与具体的函数打交道。

std::packaged_task<>模版类的模版参数是一个函数签名,比如void()就是一个没有输入参数,返回值为空的函数。传递给std::packaged_task<>的函数返回值也带表了get_future()成员函数的额返回值。函数签名的参数列表则用于指明任务函数的调用操作符。std::packaged_task<std::string(std::vector<char>*,int)>的部分声明如下:

template<>
class packaged_task<std::string(std::vector<char>*,int)>
{
public:
    template<typename Callable>
    explicit packaged_task(Callable&& f);
    std::future<std::string> get_future();
    void operator()(std::vector<char>*,int);
};

std::packaged_task对象也是一个可调用对象,可被传入给std::function对象,传给std::thread对象作为线程函数,传给其它需要一个可调用对象的函数,也可以直接被调用。当std::packaged_task被作为函数对象调用时,传递给operator()的参数将被传递给packaged_task嗦包含的函数,返回值作为异步操作的结果被存入std::future中,通过get_future()可以得到这个结果。执行std::packaged_task后,在需要执行结果时,可以等待期望被满足。

下面是一个具体的例子。很多GUI框架需要由特定的线程来完成GUI的更新。如果其它线程需要更新GUI,它必须发送一个消息给正确的线程。std::packaged_task提供了一个不需要为每种GUI相关行为自定义消息的实现方案:

#include <iostream>
#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>
#include <chrono>

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

static int shutdown = 3;

bool gui_shutdown_message_received()
{
	// 执行3次则结束GUI线程
	return ((shutdown--) < 0);
}
void get_and_process_gui_message()
{

}

// 倒计时3秒
void countdown()
{
	int from = 3, to = 0;
  	for (int i=from; i!=to; --i)
	{
    	std::cout << i << '\n';
    	std::this_thread::sleep_for(std::chrono::seconds(1));
  	}
  	std::cout << "Lift off!\n";
}

// GUI线程
void gui_thread()
{
	// 循环直到收到结束消息
    while(!gui_shutdown_message_received())
    {
    	std::cout << "gui_thread" << std::endl;
    	// 获取GUI消息并处理
        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();
        }
        // 释放mutex,执行任务
        task();
    }
}

std::thread gui_bg_thread(gui_thread);

template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
	std::cout << "post_task_for_gui_thread" << std::endl;
	// 创建packaged_task,任务函数为countdown
    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;
}

int main()
{
	std::cout << "main" << std::endl;

	std::future<void> f = post_task_for_gui_thread(countdown);
	std::cout << "posted 1" << std::endl;

	post_task_for_gui_thread(countdown);
	std::cout << "posted 2" << std::endl;

	// 等待第一个GUI线程的任务执行完成
	f.get();
	std::cout << "got" << std::endl;

    gui_bg_thread.join();

}

程序执行效果如下,main函数中,创建两个post_task_for_gui_thread对象,当GUI线程执行时,先执行一次倒计时任务,第一次倒计时任务执行完毕,main线程继续执行(打印got),然后执行第二次倒计时任务。

maingui_thread

post_task_for_gui_thread
posted 1
post_task_for_gui_thread
posted 2
3
2
1
Lift off!
gui_thread
got
3
2
1
Lift off!
gui_thread
gui_thread

--------------------------------
Process exited after 6.243 seconds with return value 0
请按任意键继续. . .

这个例子使用std::packaged_task<void()>创建任务,其包含了一个无参数无返回值的函数或可调用对象(如果当这个调用有返回值时,返回值会被丢弃)。这可能是最简单的任务,如你之前所见,std::packaged_task也可以用于一些复杂的情况——通过指定一个不同的函数签名作为模板参数,你不仅可以改变其返回类型(因此该类型的数据会存在期望相关的状态中),而且也可以改变函数操作符的参数类型。这个例子可以简单的扩展成允许任务运行在图形界面线程上,且接受传参,通过std::future返回值,而不仅仅作为任务完成一个标志。

如果有些任务不能用简单的函数调用来表示,则可以使用第三种“期望”来解决:使用std::promise对值进行显示设置。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 17:28:02

[C++11 并发编程] 14 关联任务与期望的相关文章

并发编程 14—— 线程池 之 原理一

Java并发编程实践 目录 并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线

C++11 并发编程基础(一):并发、并行与C++多线程

正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C++程序员来说是值得关注的. 回到顶部 1. 何为并发 并发指的是两个或多个独立的活动在同一时段内发生.生活中并发的例子并不少,例如在跑步的时候你可能同时在听音乐:在看电脑显示器的同时你的手指在敲击键盘.这时我们称我们大脑并发地处理这些事件,只不过我们大脑的处理是有次重点的:有时候你会更关注你呼吸的

使用 C++11 并发编程入门

一.认识并发和并行 先将两个概念, 并发与并行 并发:同一时间段内可以交替处理多个操作: 图中整个安检系统是一个并发设计的结构.两个安检队列队首的人竞争这一个安检窗口,两个队列可能约定交替着进行安检,也可能是大家同时竞争安检窗口(通信).后一种方式可能引起冲突:因为无法同时进行两个安检操作.在逻辑上看来,这个安检窗口是同时处理这两个队列 并行:同一时刻内同时处理多个操作: 图中整个安检系统是一个并行的系统.在这里,每个队列都有自己的安检窗口,两个队列中间没有竞争关系,队列中的某个排队者只需等待队

C++11并发编程:原子操作atomic

一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一个线程执行过程中可能被内核临时挂起,这就是线程切换,当内核再次切换到该线程时,之前的数据可能已被修改,不能保证原子操作. C++11提供了个原子的类和方法atomic,保证了多线程对变量原子性操作,相比加锁机制mutex.locak(),mutex.unlocak(),性能有几倍的提升. 所需头文件

[C++11 并发编程] 04 动态选择并发线程的数量

C++标准模板库提供了一个辅助函数 - std::thread::hardware_concurrency(),通过这个函数,我们可以获取应用程序可以真正并发执行的线程数量.下面这个例子,实现了一个并发版本的std::accumulate,它将工作拆分到多个线程中,为了避免过多线程带来的开销,程序指定了每个线程处理数据的最小数量. 头文件和求和操作: #include <thread> #include <numeric> #include <algorithm> #i

[C++11 并发编程] 06 - Mutex 死锁

假设有两个线程,在执行某些操作时,都需要锁定一对mutex,线程A锁定了mutex A,而线程B锁定了额mutex B,它们都在等待对方释放另一个mutex,这就会导致这两个线程都无法继续执行.这种情况就是死锁. 避免死锁最简单的方法是总是以相同的顺序对两个mutex进行锁定,比如总是在锁定mutex B之前锁定mutex A,就永远都不会死锁. 假设有一个操作要交换同一个类的两个实例的内容,为了交换操作不被并发修改影响,我们需要锁定这两个实例内部的mutex.但是,如果选定一个固定的顺序来锁定

[C++11 并发编程] 01 - Hello World

C++11标准支持了并发,其中包含了线程管理,共享资源保护,线程间同步操作和底层原子操作等功能.我们先通过一个简单的示例看看C++11标准的多线程程序是什么样的. #include <iostream> #include <thread> // 引用用于管理线程的类的头文件 using namespace std; // 线程的入口函数,程序将在新创建的线程中打印log void hello() { cout << "Hello Concurrent Worl

C++11并发编程

C++11开始支持多线程编程,之前多线程编程都需要系统的支持,在不同的系统下创建线程需要不同的API如pthread_create(),Createthread(),beginthread()等.现在C++11中引入了一个新的线程库,C++11提供了新头文件,主要包含 <thread>.<mutex>.<atomic>.<condition_varible>.<future>五个部分;<thread>等用于支持多线程,同时包含了用于启

C++11并发编程入门

也许有人会觉得多线程和并发难用,复杂,还会让代码出现各种各样的问题,不过,其实它是一个强有力的工具,能让程序充分利用硬件资源,让程序运行得更快. 何谓并发: 两个或更多独立得活动同时发生.计算机中就是单个系统同时执行多个独立的任务,通过这个任务做一会儿,再切换到别的任务再做一会儿的方式,让任务看起来是并行执行的.切换就是做上下文切换,会有时间开销,操作系统为当前运行的任务保存CPU的状态和指针,计算出要切换到哪一个任务,并为即将切换到的任务重新加载处理器状态. 并发的方式: 多进程并发 多线程并