当前位置: 首页 > article >正文

C++并发编程指南02

文章目录

      • 线程的基本操作
        • 2.1.1 启动线程
          • 基本启动方法
          • 使用函数对象
          • 避免“最令人头痛的语法解析”
        • 2.1.2 等待线程完成
          • 使用`join()`等待线程结束
          • 使用`detach()`分离线程
          • RAII方式等待线程完成
        • 2.1.3 特殊情况下的等待
        • 2.1.4 后台运行线程
          • 示例:使用分离线程处理文档
      • 总结

线程的基本操作

2.1.1 启动线程

每个C++程序至少包含一个执行main()函数的主线程。其他线程可以与主线程并行运行,当线程完成其指定任务(即函数执行完毕)时,它们也会自然退出。启动新线程通常通过创建std::thread对象来实现。

基本启动方法
void do_some_work();
std::thread my_thread(do_some_work);
  • 包含头文件:需要包含<thread>以使用C++标准库中的多线程功能。
  • 构造函数std::thread可以通过函数指针或函数对象进行构造,并在创建时自动启动线程。
使用函数对象
class background_task {
public:
  void operator()() const {
    do_something();
    do_something_else();
  }
};

background_task f;
std::thread my_thread(f);
  • 复制机制:函数对象会被复制到新线程的存储空间中,在那里执行和调用。
避免“最令人头痛的语法解析”

传递临时变量而非命名变量可能导致编译器误将其解析为函数声明:

// 错误示例
std::thread my_thread(background_task());

// 正确示例
std::thread my_thread((background_task()));  // 1
std::thread my_thread{background_task()};    // 2
  • Lambda表达式:避免上述问题的有效方法之一是使用Lambda表达式:
std::thread my_thread([]{
  do_something();
  do_something_else();
});
2.1.2 等待线程完成

为了确保线程正确结束,可以使用join()detach()方法。

使用join()等待线程结束
std::thread my_thread(do_some_work);
my_thread.join();  // 主线程等待my_thread完成
  • 作用join()使主线程等待直到新线程完成其任务。
使用detach()分离线程
struct func {
  int& i;
  func(int& i_) : i(i_) {}
  void operator() () {
    for (unsigned j = 0; j < 1000000; ++j) {
      do_something(i);  // 潜在访问隐患:空引用
    }
  }
};

void oops() {
  int some_local_state = 0;
  func my_func(some_local_state);
  std::thread my_thread(my_func);
  my_thread.detach();  // 不等待线程结束
}  // 函数返回后,some_local_state被销毁,但新线程可能仍在运行
  • 风险:如果线程还在运行而函数已返回,可能会导致对已销毁局部变量的非法访问,造成未定义行为。
RAII方式等待线程完成

为了避免异常情况下线程资源泄露,可以使用RAII模式:

class thread_guard {
  std::thread& t;
public:
  explicit thread_guard(std::thread& t_) : t(t_) {}
  ~thread_guard() {
    if (t.joinable()) {
      t.join();
    }
  }
  thread_guard(thread_guard const&) = delete;
  thread_guard& operator=(thread_guard const&) = delete;
};

void f() {
  int some_local_state = 0;
  func my_func(some_local_state);
  std::thread t(my_func);
  thread_guard g(t);
  do_something_in_current_thread();
}  // 自动调用thread_guard的析构函数,确保线程正确结束
2.1.3 特殊情况下的等待

在某些情况下,需要更灵活地控制线程的等待,例如超时等待或检查线程是否已经结束。这些需求可以通过条件变量和future等机制实现。

2.1.4 后台运行线程

使用detach()可以让线程在后台独立运行,脱离与主线程的直接联系:

std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
  • 守护线程:分离线程通常称为守护线程,适用于长时间运行的任务,如文件系统监控、缓存清理等。
示例:使用分离线程处理文档
void edit_document(std::string const& filename) {
  open_document_and_display_gui(filename);
  while (!done_editing()) {
    user_command cmd = get_user_input();
    if (cmd.type == open_new_document) {
      std::string const new_name = get_filename_from_user();
      std::thread t(edit_document, new_name);  // 1
      t.detach();  // 2
    } else {
      process_user_input(cmd);
    }
  }
}
  • 传参启动线程:不仅可以向std::thread构造函数传递函数名,还可以传递函数所需的参数。

总结

通过掌握std::thread对象的创建、启动、等待和分离等基本操作,可以有效地管理并发任务。使用RAII模式可以确保即使在异常情况下也能正确释放线程资源。对于长时间运行的任务,分离线程提供了一种简便的方式来管理后台任务。


http://www.kler.cn/a/525460.html

相关文章:

  • 生成模型:扩散模型(DDPM, DDIM, 条件生成)
  • oracle比较一下统计信息差异吧
  • Android NDK
  • [蓝桥杯 2014 省 AB] 蚂蚁感冒
  • PWM频率测量方法
  • Node.js与MySQL模块结合:打造安全高效的用户信息管理系统
  • 动手学图神经网络(8):在消息传递中定制聚合操作
  • 什么是 AI 代理?
  • redis中n是什么含义?
  • 从春晚《秧BOT》来看人形机器人与四足机器人的区别
  • IPhone13 Pro Max设备详情
  • 寒假学web--day06
  • arkui-x跨平台与android java联合开发
  • 解读隐私保护工具 Fluidkey:如何畅游链上世界而不暴露地址?
  • 微服务学习-服务调用组件 OpenFeign 实战
  • 四.4 Redis 五大数据类型/结构的详细说明/详细使用( zset 有序集合数据类型详解和使用)
  • Tez 0.10.1安装
  • Rust语言进阶之zip用法实例(九十五)
  • 简化配置与动态表达式的 Spring EL
  • 【硬件介绍】三极管工作原理(图文+典型电路设计)
  • 守护进程和nuhup、的区别
  • 【高内聚】设计模式是如何让软件更好做到高内聚的?
  • JavaWeb学习-SpringBotWeb开发入门(HTTP协议)
  • 1.2 基于深度学习的底层视觉技术
  • 基于微信小程序的电子商城购物系统设计与实现(LW+源码+讲解)
  • 如何创建一个 Vue 3 项目