「C++」掌握C++异步编程

本文介绍了C++中异步任务的基本概念,包括异步任务的定义、异步与多线程的优缺点,重点讲解了future、promise、async和packaged_task的使用及其在提高程序性能和处理异步结果中的作用。
摘要由CSDN通过智能技术生成
在这里插入图片描述


📄前言

异步任务是多线程编程的核心,若想学习多线程设计,深入了解这些基本概念是必不可少的。如果你从未了解过这些概念,亦或者对c++异步任务的库函数有所遗忘了,不妨点进本文来学习一下。

异步任务

概念

异步任务就是在程序后台执行的任务,而异步指的是与主线程所不同步奏。用生活的例子来讲就是,我在一边看视频,一边吃饭。创建一个线程,让它执行一个函数,这就是一个简单的异步任务。你可能会觉得这也太简单了,但先不要走,其实我们要讨论的还不止如此。

C++为异步任务提供的可不止thread,还有为了异步任务返回值而诞生的 期待(future) 承诺(promise),以及打包任务所需的 packaged_task 和 用更高级的异步任务创建工具—async

惯例地讨论下优缺点:

  • 异步任务的优点:
    • 提高程序的性能:多个线程并发运行能够显著提高程序的性能(前提是有合理的设计)。
    • 提高程序响应性:一个程序可能需要等待I/O输入的同时,能够同时处理后台的各种任务(如网络数据传输)。
  • 异步任务的缺点
    • 让程序调试难度提高:多线程的 bug 可能难以重现、甚至只会在特定的机器出现问题。
    • 可能会导致死锁、竞态条件等问题:不合理的设计可能会导致程序的性能提高不显著,甚至导致程序无响应。

期待与承诺

如果你有使用过 thread 函数,那么你肯定会发现它是无法通过函数返回值来查看运算结果,虽然可以通过引用传参来获取返回值,但这样获取的数据在多线程情况下还得自己解决数据二义性问题,而 future 和 promise 提供了一种线程安全且方便的返回方式

future

future 如同其名—期待,期待一个任务结果的获取,我们不需要立刻知道结果是否就绪,只需要在我们需要用到结果时才去访问。如果此时结果还未就绪,线程就会阻塞等待这个结果的获取。

用一个生活的小例子来比喻就是:我和朋友约定去公园一起玩,我在去公园的路上不知道朋友是否已经到达,只有我到了公园才知道,我当然会期待到公园的时候他就已经到达,但如果他还没到公园,我就在此地等待。相信这个例子已经足够说明期待的本质了,在C++中 future 一般与 async 、promise、package_task 等工具一起使用

简单的函数使用演示:

#include <future>
#include <iostream>
#include <thread>
int sum(int x, int y) {
    std::cout << "线程运行ing" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));	// 模拟运行任务中。
    std::cout << "线程结束" << std::endl;
    return x + y;
}

int main() {
    std::future<int> res = std::async(sum, 1, 9);	// async的返回值是一个future。详细将在后文介绍。
    std::cout << res.get() << std::endl;	// res还未待续,线程阻塞等待res。
    
    return 0;
}

promise

promise 与 future 也是一样的“人与其名”,承诺会给 future 一个结果,而这个结果可能会及时得到,也可能会非常晚才得到。promise 需要与 future 一同使用,一般通过传参使用。

函数使用:

void func(std::promise<std::string>& ip, std::promise<std::string>& msg) {
    // 假设进行网络连接
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ip.set_value("8.8.8.8");    //获取客户端ip
    msg.set_value("Hello!");         // 获取客户端的信息.
}

int main() {
    std::promise<std::string> get_ip, get_msg;
    std::future<std::string> ip = get_ip.get_future();	// 绑定期待
    std::future<std::string> msg = get_msg.get_future();
    // 引用参数需要使用ref来保证其引用性质。
    std::thread t1(func, std::ref(get_ip), std::ref(get_msg));
    
    t1.detach();	//分离线程
    printf("[%s] %s", ip.get().c_str(), msg.get().c_str());

    return 0;
}

异常处理

future 和 promise可以用于接受异常,这也是它们的一大特点之一,如果使用 async 中发生异常,则异常会存储到它的返回值中,当调用 get() 时再次被抛出。当然使用promise也能够设置异常,然后让future 接收。

int func(int x, int y)
{
    if(y == 0)
        throw std::runtime_error("x / 0");
    else
        return x / y;
}

void errorfunc(std::promise<int>& ret)
{
    try
    {
        int res = func(29, 0);
        ret.set_value(res);
    }catch(const std::runtime_error& e)
    {   // promise 也能够储存异常
        ret.set_exception(std::current_exception() );
    }
}


int main() {
    try {
        std::promise<int> ret;
        std::future<int> f = ret.get_future();  //绑定promise
        std::thread(errorfunc, std::ref(ret)).detach();
        int result = f.get();   //异常在get()函数被抛出
        std::cout << result << std::endl;
    }catch(const std::runtime_error& e)
    {
        std::cerr << "error: " << e.what() << std::endl;
    }

    return 0;
}

执行异步任务

async

async 是 C++ 中更智能的一种创建线程的方式,它能够自动管理线程的生命周期,并且自动控制线程的 数量(程序线程过多将不会创建),它的返回值是一个带函数返回值的 future ,可以用它来得知函数的运行结果 或 函数发生的异常。

  • async 的优点:
    • 自动管理线程生命周期:线程不需要自己来管理,函数将自己管理。
    • 异常安全:future能接受函数中产生的异常

函数使用:

template <typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init)
{
    //获取数组的长度
    const unsigned long length = std::distance(first, last);

    const unsigned long min_per_thread = 25; 
    // 分块计算数据
    if(length <= 25)    //递归终点 
        return std::accumulate(first, last, init); 
    else
    {
        Iterator mid_point = first + length / 2; 
        // async会自动处理线程的数量,不用担心线程无限开销。 
        std::future<T> first_half_result = 
                std::async(parallel_accumulate<Iterator, T>, mid_point, last, init);   //处理后半部分


        // 处理前半数据	(注意这里的init要换成T())
        return first_half_result.get() + std::accumulate(first, mid_point, T());

    }
}

int main() {
    std::vector<int> nums(100);
    // 从1开始为数组填充数据1~100
    std::iota(nums.begin(), nums.end(), 1);
	
    auto sum = parallel_accumulate(nums.begin(), nums.end(), 123);
//    auto sum = std::accumulate(nums.begin(), nums.end(), 123);

    return 0;
}

// 函数递归展开图

                                          [0-100)
                                             |
                      +-----------------------------------------+
                      |                                         |
                  [0-50)                                 [50-100)
                      |                                         |
          +-------------------+                      +-------------------+
          |                   |                      |                   |
      [0-25)             [25-50)                [50-75)             [75-100)
        |                   |                      |                   |
   std::accumulate   std::accumulate        std::accumulate     std::accumulate

packaged_task

packaged_task 是用于打包异步任务的工具,它可以对普通函数、类内函数、lambda函数进行打包,然后在另一个线程中进行运行,经常用于像线程池等需要打包任务的场景。

简单的函数使用:

// 函数使用
int func(int x, int y)
{
    return x + y;
}

int main() {
    std::packaged_task<int(int, int)> task1(func);	//包装普通函数
    std::packaged_task<int()> task2([&] { return func(2, 3); });	// 包装lambda表达式

    std::future<int> res1 = task1.get_future();	// packaged_task 返回值是future
    std::future<int> res2 = task2.get_future();

    std::thread t1(std::move(task1), 9, 9).detach;	
    std::thread t2(std::move(task2) ).detach;

    std::cout << res1.get() << ":" << res2.get() << std::endl;

    return 0;
}

📓总结

在C++中异步任务常用的工具有future、promise、async、packaged_task等,掌握它们对编写一个高效的多线程至关总要,希望本文能够对你有所帮助。

工具用途
std::async用于以简化的方式启动异步任务,自动管理线程生命周期,并自动控制线程数量,返回std::future对象以获取任务的执行结果或异常。
std::future提供一种机制来访问异步操作的结果。当异步操作完成时,可以通过std::future对象获取结果或捕获在异步操作中抛出的异常。
std::promise允许在某个线程中设置值或异常,这些值或异常将在未来某个时刻通过与之关联的std::future对象被其他线程访问。
std::packaged_task封装一个可调用对象,并允许其异步执行,同时提供一个std::future对象,以便获取该可调用对象的返回值或在执行过程中捕获的异常。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值