C++异步编程最佳实践

简介: ## Mapreduce问题 多个数据,进行同类型计算,最后汇总结果,怎样用C++解锁此类问题? >这个最简单了,单线程循环处理每份数据好了。 >这有何难,创建一块地方,针对每份数据创建个线程执行计算,将结果写入先前创建的数据的对应地方,等各线程结束,完活。 上面的说法都对,只是不够好。 对于单线程处理,在这个多核时代,未免大马拉小车,有点浪费CPU。当问题规模变大,你准备花多

Mapreduce问题

多个数据,进行同类型计算,最后汇总结果,怎样用C++解锁此类问题?

这个最简单了,单线程循环处理每份数据好了。
这有何难,创建一块地方,针对每份数据创建个线程执行计算,将结果写入先前创建的数据的对应地方,等各线程结束,完活。

上面的说法都对,只是不够好。
对于单线程处理,在这个多核时代,未免大马拉小车,有点浪费CPU。当问题规模变大,你准备花多长时间计算。
对于多线程这个说法,从策略上来说,没有问题。但问题是你不得不手动启动线程,把结果放到共享内存里,通过thread.join()等待结束。这点操作难度,对于我大C++开发来说算啥,分分钟就搞定了。但问题又来了,现在你的老板问你要针对单份数据的处理函数,返回结果。
呵呵,原来的单份数据处理函数,十有八九长成这样:

RetType deal(Indata t,Retdata& r)
{
    r=balabala;
}

为多线程搞的那个为向主线程传递结果,不得不处理共享内存,现在你又要函数式操作,这绝逼不能够啊,于是乎,你不得不又写了一个:

RetType deal(Indata t)
{
Retdata r;
deal(t,r);
return r;
}

呵呵,我似乎闻到了坏代码的味道。

更好的方式

C++11中提供了操作多线程的高层次特性。

  • std::packaged_task 包装的是一个异步操作,相当与外包任务,好比我大阿里把电话客服外包给某某公司。
  • std::future 提供了一个访问异步操作结果的机制,这个是底层机制,在packaged_task和promise内部都有future来访问结果。

说的比较干巴,还是上代码吧!

#include <iostream>
#include<vector>
#include <future>
using namespace std;

long long jiechen(int n)
{
    long long ret=1;
    for (int i=1;i<=n; i++)
        ret*=i;
    return ret;

}

int main()
{
    vector<int> data={9,11,13,17,19};
    vector<future<long long>> fus;
    for (auto i:data)
    {
        packaged_task<long long() > pt(bind(jiechen,i));
        fus.push_back(std::move(pt.get_future()));
        std::thread(std::move(pt)).detach();
    }
    for(auto& i:fus)
    {
        i.wait();
        cout<<i.get()<<endl;
    }
    return 0;
}

比较简单,就没必要解释了。跟“古老”的方案比,还是有些不同的

1. 不关心线程创建和结束
2. 使用层面无共享数据,这个意味着写子线程函数时你不用小心翼翼担心线程安全问题。
3. 抽象层次不同,古老方案本质上还是依靠负作用编程,新标准异步方式则某种程度上跳出了这个范畴。
4,如有异常发生,新方案可以在外层处理异常。

上面说了那么多,肯定还是有同学说,引入概念(计算机领域的概念真是多啊),变的难理解了,那有我直接操作线程干活爽。我又想要好处,又想好理解,有简单的方式吗,有简单的方式吗,有简单的方式吗?
还真有:std::async

最佳实践

std::async的原型async(std::launch::async | std::launch::deferred, f, args...)

std::launch::async:在调用async就开始创建线程。
std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait时才创建线程。

std::async是让大家透懒的,工作过程是这样的:async先将异步操作用packaged_task包装起来,然后将异步操作的结果放到std::promise中,这个过程就是创造未来的过程。外面再通过future.get/wait来获取这个未来的结果。完全可以理解为:
async(func,args).get() 就是启动线程执行func并获取返回值。
应用这个特性,上面的主线程代码变为:

int main()
{
    vector<int> data={9,11,13,17,19};
    vector<future<long long>> fus;
    for (auto i:data) fus.push_back(std::async(jiechen,i));
    for(auto& i:fus)
    {
        i.wait();
        cout<<i.get()<<endl;
    }

    return 0;
}

std:async 不是弄出来捣乱的玩意,真的是异步编程利器,至少,在跨平台时不用改代码不是。在项目中,大家应该尽力避免直接调用低层次接口,直接调用async创建新线程。
以上是一家之言,难免偏颇,欢迎探讨。

目录
相关文章
|
3小时前
|
存储 安全 算法
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
74 5
|
3小时前
|
消息中间件 负载均衡 监控
【ZMQ PUB模式指南】深入探究ZeroMQ的PUB-SUB模式:C++编程实践、底层原理与最佳实践
【ZMQ PUB模式指南】深入探究ZeroMQ的PUB-SUB模式:C++编程实践、底层原理与最佳实践
275 1
|
4小时前
|
C++
C++ 编程必备:对象生命周期管理的最佳实践
在C++中,对象的生命周期是指对象存在的时间段,从对象创建到对象销毁的整个过程。正确地管理对象的生命周期是编写高效、可靠C++代码的关键之一
70 1
|
3小时前
|
NoSQL API Redis
最佳实践|如何使用c++开发redis module
本文将试着总结Tair用c++开发redis module中遇到的一些问题并沉淀为最佳实践,希望对redis module的使用者和开发者带来一些帮助(部分最佳实践也适用于c和其他语言)。
76268 0
|
3小时前
|
安全 vr&ar C++
C++:编程语言的演变、应用与最佳实践
C++:编程语言的演变、应用与最佳实践
|
3小时前
|
程序员 开发工具 git
【程序员英语 代码提交】C++工程师的代码提交艺术:git commit 时 精确表达与最佳实践
【程序员英语 代码提交】C++工程师的代码提交艺术:git commit 时 精确表达与最佳实践
120 1
|
3小时前
|
设计模式 安全 C++
【C++ const 函数 的使用】C++ 中 const 成员函数与线程安全性:原理、案例与最佳实践
【C++ const 函数 的使用】C++ 中 const 成员函数与线程安全性:原理、案例与最佳实践
243 2
|
3小时前
|
安全 搜索推荐 Linux
Linux C++ 环境下数据高效备份策略:全面指南与最佳实践
Linux C++ 环境下数据高效备份策略:全面指南与最佳实践
37 1
|
3小时前
|
安全 算法 编译器
【C++ 泛型编程 进阶篇】深入探索 C++ STL 容器的嵌套类型:识别、运用与最佳实践
【C++ 泛型编程 进阶篇】深入探索 C++ STL 容器的嵌套类型:识别、运用与最佳实践
98 7
|
3小时前
|
存储 安全 编译器
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
124 0