C++并发编程指南02

线程的基本操作

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模式可以确保即使在异常情况下也能正确释放线程资源。对于长时间运行的任务,分离线程提供了一种简便的方式来管理后台任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金_chihiro_修行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值