当前位置:首页 > 单片机 > 程序喵大人

在 C++11 引入的 std::thread 中,每个线程对象在其生命周期结束前必须调用 join()detach() 方法之一。这两个方法代表了两种不同的线程管理策略。

本文仅代表个人观点

join()

join() 方法会阻塞当前线程,直到被调用的线程执行完成。调用 join() 后,主线程会等待子线程结束,然后继续执行。

特点

  • 阻塞性:调用 join() 的线程会被阻塞,直到目标线程完成
  • 资源回收:线程结束后,系统会自动回收线程资源
  • 同步机制:提供了线程间的同步点,确保子线程在主线程继续之前完成

示例代码

#include #include #include  void worker_function(int id) {  std::cout << "线程 " << id << " 开始工作\n";  std::this_thread::sleep_for(std::chrono::seconds(2));  std::cout << "线程 " << id << " 完成工作\n"; }  int main() {  std::cout << "主线程开始\n";    std::thread t1(worker_function, 1);  std::thread t2(worker_function, 2);    std::cout << "等待线程完成...\n";    // 使用 join() 等待线程完成  t1.join();  t2.join();    std::cout << "所有线程完成,主线程结束\n";  return 0; } 

detach()

detach() 方法会将线程从 std::thread 对象中分离,使线程在后台独立运行。分离后的线程无法再被 join(),也无法直接控制。

特点

  • 非阻塞性:调用 detach() 后立即返回,不会阻塞当前线程
  • 脱离控制:分离的线程无法被主线程控制,主线程无法等待其完成
  • 资源管理复杂:需要确保分离的线程访问的资源在线程结束前保持有效

这里的主线程不一定是绝对意义上的进程的主线程,也可以是创建子线程的那个线程,也可以是其它能拿到thread对象的线程。

示例代码

#include #include #include  void background_task(int id) {  std::cout << "后台线程 " << id << " 开始工作\n";  std::this_thread::sleep_for(std::chrono::seconds(3));  std::cout << "后台线程 " << id << " 完成工作\n"; }  int main() {  std::cout << "主线程开始\n";    std::thread t1(background_task, 1);    // 使用 detach() 分离线程  t1.detach();    std::cout << "线程已分离,主线程继续执行\n";    // 主线程可能在子线程完成前就结束  std::this_thread::sleep_for(std::chrono::seconds(1));  std::cout << "主线程结束\n";    return 0; } 

为什么不推荐使用 detach()

1. 资源生命周期管理困难

当使用 detach() 时,分离的线程脱离了主线程的控制,无法保证执行顺序,这会导致以下问题:

class DataProcessor { private:  std::vectordata;   public:  void process_in_background() {  std::thread t([this]() {  for (int& item : data) {  // 危险!data 可能已被销毁  item *= 2;  std::this_thread::sleep_for(std::chrono::milliseconds(100));  }  });  t.detach();  // 问题:无法控制线程和对象销毁的顺序  } };  int main() {  {  DataProcessor processor;  processor.process_in_background();  }  // processor 在这里被销毁,但无法确保分离的线程已完成    return 0; } 

2. 无法控制执行顺序和完成时机

#include #include #include  void write_log(const std::string& message) {  std::ofstream file("log.txt", std::ios::app);  std::this_thread::sleep_for(std::chrono::seconds(2));  file << message << std::endl; // 无法保证在程序结束前完成 }  int main() {  std::thread logger(write_log, "重要日志信息");  logger.detach();    // 主线程结束,无法确保日志写入完成  return 0; } 

推荐使用 join() 的原因

1. 确保资源完整性

class SafeDataProcessor { private:  std::vectordata;   public:  void process_data() {  std::thread t([this]() {  for (int& item : data) {  item *= 2;  std::this_thread::sleep_for(std::chrono::milliseconds(100));  }  });    t.join();  // 确保线程在对象销毁前完成  } }; 

2. 异常安全,RAII核心还是确保资源完整性

class ThreadManager { private:  std::vectorthreads;   public:  ~ThreadManager() {  // RAII 模式确保所有线程在析构前完成  for (auto& t : threads) {  if (t.joinable()) {  t.join();  }  }  }    void add_task(std::function task) {  threads.emplace_back(task);  } }; 

最佳实践

1. 使用 RAII 管理线程

class ThreadRAII { private:  std::thread t;   public:  template  ThreadRAII(F&& f, Args&&... args)   : t(std::forward(f), std::forward(args)...) {}    ~ThreadRAII() {  if (t.joinable()) {  t.join();  }  }    // 禁止拷贝  ThreadRAII(const ThreadRAII&) = delete;  ThreadRAII& operator=(const ThreadRAII&) = delete;    // 允许移动  ThreadRAII(ThreadRAII&&) = default;  ThreadRAII& operator=(ThreadRAII&&) = default; }; 

2. 使用线程池代替 detach

这个写代码有点麻烦,不列出了。

总结

  • join() 提供了更好的资源管理和程序控制,确保线程在适当的时机完成
  • detach() 虽然提供了非阻塞的执行方式,但失去了对线程的控制,无法保证执行顺序和资源安全
  • 在大多数情况下,推荐使用 join() 或更高级的线程管理机制(如线程池)
  • 如果确实需要后台线程,考虑使用专门的线程池或异步任务框架
  • 始终记住:每个 std::thread 对象在销毁前必须调用 join() 或 detach(),否则程序会调用 std::terminate()


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭