C++11多线程-异步运行(1)之std::promise

前面介绍了C++11的std::thread、std::mutex以及std::condition_variable,并实现了一个多线程通信的chan类,虽然由于篇幅的限制,该实现有些简陋,甚至有些缺陷,但对于一般情况应该还是够用了。在C++11多线程系列的最后会献上chan的最终版本,敬请期待。
本文将介绍C++11的另一大特性:异步运行(std::async)。async顾名思义是将一个函数A移至另一线程中去运行。A可以是静态函数、全局函数,甚至类成员函数。在异步运行的过程中,如果A需要向调用者输出结果怎么办呢?std::async完美解决了这一问题。在了解async的解决之道前,我们需要一些知识储备,那就是:std::promise、std::packaged_task和std::future。异步运行涉及的内容较多,我们会分几节来讲。

1. std::promise

std::promise是一个模板类: template<typename R> class promise。其泛型参数R为std::promise对象保存的值的类型,R可以是void类型。std::promise保存的值可被与之关联的std::future读取,读取操作可以发生在其它线程。std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future亦然。std::promise和std::future合作共同实现了多线程间通信。

1.1 设置std::promise的值

通过成员函数set_value可以设置std::promise中保存的值,该值最终会被与之关联的std::future::get读取到。需要注意的是:set_value只能被调用一次,多次调用会抛出std::future_error异常。事实上std::promise::set_xxx函数会改变std::promise的状态为ready,再次调用时发现状态已要是reday了,则抛出异常。

#include <iostream> // std::cout, std::endl
#include <thread>   // std::thread
#include <string>   // std::string
#include <future>   // std::promise, std::future
#include <chrono>   // seconds
using namespace std::chrono;

void read(std::future<std::string> *future) {
    // future会一直阻塞,直到有值到来
    std::cout << future->get() << std::endl;
}

int main() {
    // promise 相当于生产者
    std::promise<std::string> promise;
    // future 相当于消费者, 右值构造
    std::future<std::string> future = promise.get_future();
    // 另一线程中通过future来读取promise的值
    std::thread thread(read, &future);
    // 让read等一会儿:)
    std::this_thread::sleep_for(seconds(1));
    // 
    promise.set_value("hello future");
    // 等待线程执行完成
    thread.join();

    return 0;
}
// 控制台输: hello future

与std::promise关联的std::future是通过std::promise::get_future获取到的,自己构造出来的无效。一个std::promise实例只能与一个std::future关联共享状态,当在同一个std::promise上反复调用get_future会抛出future_error异常。
共享状态。在std::promise构造时,std::promise对象会与共享状态关联起来,这个共享状态可以存储一个R类型的值或者一个由std::exception派生出来的异常值。通过std::promise::get_future调用获得的std::future与std::promise共享相同的共享状态。

1.2 当std::promise不设置值时

如果promise直到销毁时,都未设置过任何值,则promise会在析构时自动设置为std::future_error,这会造成std::future.get抛出std::future_error异常。

#include <iostream> // std::cout, std::endl
#include <thread>   // std::thread
#include <future>   // std::promise, std::future
#include <chrono>   // seconds
using namespace std::chrono;

void read(std::future<int> future) {
    try {
        future.get();
    } catch(std::future_error &e) {
        std::cerr << e.code() << "\n" << e.what() << std::endl;
    }
}

int main() {
    std::thread thread;
    {
        // 如果promise不设置任何值
        // 则在promise析构时会自动设置为future_error
        // 这会造成future.get抛出该异常
        std::promise<int> promise;
        thread = std::thread(read, promise.get_future());
    }
    thread.join();

    return 0;
}

上面的程序在Clang下输出:

future:4
The associated promise has been destructed prior to the associated state becoming ready.

1.3 通过std::promise让std::future抛出异常

通过std::promise::set_exception函数可以设置自定义异常,该异常最终会被传递到std::future,并在其get函数中被抛出。

#include <iostream>
#include <future>
#include <thread>
#include <exception>  // std::make_exception_ptr
#include <stdexcept>  // std::logic_error

void catch_error(std::future<void> &future) {
    try {
        future.get();
    } catch (std::logic_error &e) {
        std::cerr << "logic_error: " << e.what() << std::endl;
    }
}

int main() {
    std::promise<void> promise;
    std::future<void> future = promise.get_future();

    std::thread thread(catch_error, std::ref(future));
    // 自定义异常需要使用make_exception_ptr转换一下
    promise.set_exception(
        std::make_exception_ptr(std::logic_error("caught")));
    
    thread.join();
    return 0;
}
// 输出:logic_error: caught

std::promise虽然支持自定义异常,但它并不直接接受异常对象:

// std::promise::set_exception函数原型
void set_exception(std::exception_ptr p);

自定义异常可以通过位于头文件exception下的std::make_exception_ptr函数转化为std::exception_ptr。

1.4 std::promise<void>

通过上面的例子,我们看到std::promise<void>是合法的。此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞。

1.5 std::promise所在线程退出时

std::async(异步运行)时,开发人员有时会对std::promise所在线程退出时间比较关注。std::promise支持定制线程退出时的行为:

  • std::promise::set_value_at_thread_exit 线程退出时,std::future收到通过该函数设置的值。
  • std::promise::set_exception_at_thread_exit 线程退出时,std::future则抛出该函数指定的异常。

关于std::promise就是这些,本文从使用角度介绍了std::promise的能力以及边界,读者如果想更深入了解该类,可以直接阅读一下源码。

上一篇
C++11多线程-条件变量
目录 下一篇
C++11多线程-packaged_task
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 接着上节 condition_varible ,本节主要介绍future的内容,练习代码地址。本文参考http:/...
    jorion阅读 14,707评论 1 5
  • 接着上节 atomic,本节主要介绍condition_varible的内容,练习代码地址。本文参考http://...
    jorion阅读 8,437评论 0 7
  • 线程的产生 多线程并发高级接口std::async()和类std::future<> 1,async()使得可调用...
    龙遁流阅读 963评论 0 1
  • 写项目的时候经常会遇到按钮上有图片和文字的情况,每次图片和按钮的位置都会有些变化,经常要调,没有难点,却很麻烦,所...
    黯魂粉玉阅读 192评论 0 0
  • 在许多论坛里都看到过“什么东西吃了不会胖?”诸如此类的问题,一路看下来基本都是“西北风”、“奶嘴”,甚至“蛔虫”得...
    仙贝君阅读 10,392评论 14 75