聊聊并发编程的使用场景

文摘   2024-12-21 12:01   新加坡  

 

你好,我是雨乐~

今天,我们聊聊C++中一个非常重要的特性,即如何提升程序性能

一般提到这个问题,第一时间想到的就是并发,即将尽可能多的任务放到其他线程去做,类似于我们常说的MapReduce,即映射和归纳,本文的主题也类似,不过是通过各种不同的方式来实现。

同步

同步,一般指的是宏观上的单步,即运行过程(假如去掉指令重排以及CPU的乱序执行外)和结果和我们的预期一致。

先看看下面这个例子:

int main() {
  auto &&sum = [](auto &&a, auto &&b) {
    return a + b;
  };
  
  auto &&result = sum(12);
  
  // do sth
}

上面的例子很简单,定义一个lambda,其作用是求和,然后下一步对两个整数1和2进行求和操作。

嗯,如果上面的lambda是一个很重的操作,即重CPU或者重IO或者二者兼有之,这种情况下,我们就得等,等待其执行完后,再用其结果进行后续操作。假如所在的服务器是单核的话,上面这种实现完全没问题,但在多核普遍的今天,如果不把处理器的性能榨干,就说明我们的程序性能优化还缺点火候,因此,针对上述例子,准备使用多种方式进行优化。

Thread

确切的说,自C++11起,才有了自己的多线程对象或者接口来支持创建多线程程序(C++11之前,需要使用pthread等方式)。

我们使用STL中的std::thread来实现此功能。

#include <thread>

int main() {
int32_t result{};
auto &&sum = [&](auto &&a, auto &&b) {
    result = a + b;
  };

std::thread t(sum, 12);

  t.join();

return0;
}

std::async

使用std::async来实现此功能的话,可以像如下这样操作:

int main() {
auto &&sum = [](auto &&a, auto &&b) {
    return a + b;
  };

auto &&fut = std::async(sum, 12);

auto &&result = fut.get();

// do sth
return0;
}

上述代码中,重点关注两点:

1、std::async:

  • • std::async(sum, 1, 2) 会异步执行 sum lambda,将 1 和 2 作为参数传递给它。
  • • std::async 返回一个 std::future 对象 fut,它代表异步任务的结果。通过调用 fut.get(),可以等待异步任务的完成并获取其结果。

2、fut.get():

  • • fut.get() 会阻塞当前线程,直到异步任务执行完毕并返回结果。在此代码中,sum lambda 会计算 1 + 2 并返回 3
  • • fut.get() 会获取该返回值并将其赋值给 result

在默认情况下,async是新开一个线程来执行任务或者说是继续在当前线程执行,这个依赖于当时的场景。不过,可以通过指定policy来决定使用哪种:

PS:如果没有显式指定policy,则策略默认是std::launch::async | std::launch::deferred,即是使用新线程还是当前线程依赖于当时的场景。

package_task

std::packaged_task 是一个包装器,用于封装可调用对象(例如函数、lambda 表达式或函数对象)。通过 std::packaged_task,我们可以将一个可调用对象和 std::future 结合使用,异步执行该可调用对象并获取结果。

其声明如下:

templateclass Rclass ...ArgTypes >
class packaged_task<R(ArgTypes...)>;

可以像下面这样使用:

int main() {
std::packaged_task<int(intint)sum([](int a, int b){ return a + b;});

auto && r = sum(1020);

auto &&result = r.get();

// do sth
return0;
}

上面这个例子在方式上类似于直接执行一个lambda callable对象,当然了,也可以像如下这样异步操作:

int main() {
std::packaged_task<int(intint)sum([](int a, int b){ return a + b;});

auto &&future = sum.get_future();

std::thread task_td(std::move(sum), 210);
  task_td.join();
auto &&result = future.get();

// do sth
return0;
}

结语

本文从thread、async以及package_task几个角度说明了如何使用异步方式进行求值的方式,本文作为一个综合,没有进行过多的深入分析,后面将针对这几个概念进行详细分析。

下期见~

以上

如果对本文有疑问可以加笔者微信直接交流,笔者也建了C/C++相关的技术群,有兴趣的可以联系笔者加群。

推荐阅读  点击标题可跳转

1、聊聊并发编程中的这个新特性

2、再见了!atomic!!

3、呃,竟然死锁了~


雨乐聊编程
毕业于中国科学技术大学,现任某互联网公司高级技术专家一职。本公众号专注于技术交流,分享日常有意思的技术点、线上问题等,欢迎关注