C++11笔记-多线程-async()和Future

多线程高级接口:async和Future

C++11提供的多线程开发接口中分为高级接口和低级接口;
高级接口就是C++标准库中由std::async()和class std::future<>提供的高级接口;

  • async()提供了一个接口,让一个函数或者一个函数对象能够在后台运行,成为一个独立的线程;
  • class std::future<>允许你等待线程结束并获取其结果,可以获取到线程函数的返回值或者是一个异常;
    下面将使用《C++标准库》18章提供的例子和内容,展开对async和future的学习;

async()和Future的第一个用例

功能需求

需求,计算两个操作数的总和,并在控制台上输出操作数表示的ASCII码;
设定两个操作数的输出字符函数分别func1和func2函数;
串行编程模型的做法是:
func1()+func2();
执行的次序为:先执行func1()函数,再执行func2()函数;总耗时为func1函数耗时+func2函数耗时+计算总和时间;或者两个函数执行顺序相反,取决于不同编译器;
并行编程模型的做法是:
对func1函数分配一个单独线程执行;
同时执行func2函数;
在计算func1和func2的和时,等待func1函数执行完成;
总耗时为:func1或者func2两者中耗时最长的时间+计算总和时间

代码实现

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <random>
#include <exception>

using namespace std;

int doSomething(char c)
{
    default_random_engine dre(c);
    uniform_int_distribution<int> id(10, 1000);
	// 在控制台上打印字符,增加延时,防止函数很快执行完成;
    for (size_t i = 0; i < 10; i++)
    {
        this_thread::sleep_for(chrono::milliseconds(id(dre)));
        cout.put(c).flush();
    }
    return c;
}

int func1()
{
    return doSomething('.');
}

int func2()
{
    return doSomething('+');
}
int main()
{
	cout << "starting func1() in background" << " and func2() in foreground:" << endl;
	future<int> result1(async(func1));
    int result2 = func2();
    int result = result1.get() + result2;
    cout << "\n result of func1()+func2(): " << result << endl;
    system("pause");
}

运行结果如下:
C++11笔记-多线程-async()和Future

代码剖析

关于随机生成数和增加的sleep_for这部分内容不详细讲解,毕竟多线程async和future才是我们学习的目标;
与之前说过的并行编程模型的步骤相同;
函数执行的流程为:

  1. 使用async()尝试启动func1()于后台,并将结果赋值给某个future对象;在这里,async()函数尝试将其所获得的函数立即启动于一个分离线程内。大白话就是func1()放到了一个线程并开始执行,与main函数同时执行,不会造成main函数的停滞。
    关于为啥要将async()函数的结果赋值给future,原因有二:
  • future允许你取得“传给async()的那个函数”的未来结果(返回值/异常)future<>对象内是由“启动函数”返回类型的特化,如果被启动的函数的返回值为void,future则会是future<void>;
  • future对象必须存在,由于async()函数只是尝试启动函数,如果函数没有被启动成功,在后面需要使用该函数的返回值时,需要手动使用future来强行启动函数执行;所以不管你对这个被启动函数的结果是否感兴趣,都需要持有这个future对象;
  1. 在前台main函数启动func2函数,是常规的同步执行顺序,程序需要在func2函数执行完成后在执行下一条语句;
  2. 计算func1和func2的总和;
    int result = result1.get() + result2;
    

需要获取到func1函数的结果和func2函数的结果,func2的结果比较简单,result2就是该函数的返回值;func1由于分离到另外一个线程,就需要通过future对象来获取返回值,使用future的get函数获取func1函数的执行结果;
我们对future调用get时,会出现三种情况:

  • 如果func1()被async()启动于一个分离线程中并且已结束,你会立即获得结果;
  • 如果func1()被启动但尚未结束,get()会引发停滞(block)待func1函数执行结束后获得结果;
  • 如果func1()尚未启动,会被强迫启动如一个同步调用;get()会引发停滞直至产生结果;
    async函数在单线程系统中无法启动新线程,这样使用get就能保住程序的正常执行,如同同步单线程执行;这样多线程程序就会退化为单线程的程序,get只是将func1函数的执行时间延迟到了func2函数之后;

总结

一般使用async和future怎么实现一个多线程的功能;
常规的流程是:

1.引用#include <future>;
2.传递某些可并行执行的函数,交给std::async()作为一个可调用对象(callable object)。
3.将执行结果赋值给一个future<ReturnType> object。
4.当你需要那个被启动函数的执行结果,或当你想确保该函数结束,就对future<> object调用get()。

注意:这里没有考虑到线程之间对数据的竞态情况;
注意:如果没有调用get()就不保证func1()函数会被调用;
注意:如果std::async()启动的函数是一个无返回值的函数,则会产生一个future<void>对象,是future<>的一个偏特化版,这种情况下get()返回“无物”;
注意:传给async()的东西可以是任何类型的callable object:可以是函数、成员函数、函数对象(function object)或lambda表达式;

引用

1.《C++标准库》第二版

上一篇:python网络爬虫(第六章:协程的实现方法)


下一篇:守护进程,锁,队列(生产者消费者模型)