c – 安全地销毁线程池

考虑以C 14编写的普通线程池的以下实现.

> threadpool.h
> threadpool.cpp

观察每个线程正在休眠,直到它被通知唤醒 – 或者一些虚假的唤醒调用 – 并且以下谓词评估为真:

std::unique_lock<mutex> lock(this->instance_mutex_);

this->cond_handle_task_.wait(lock, [this] {
  return (this->destroy_ || !this->tasks_.empty());
});

此外,观察ThreadPool对象使用数据成员destroy_来确定它是否被销毁 – 已经调用了析构函数.将此数据成员切换为true将通知每个工作线程是时候完成其当前任务,然后任何其他排队任务与正在销毁此对象的线程同步;除了禁止入队成员的功能.

为方便起见,析构函数的实现如下:

ThreadPool::~ThreadPool() {
  {
    std::lock_guard<mutex> lock(this->instance_mutex_); // this line.

    this->destroy_ = true;
  }

  this->cond_handle_task_.notify_all();

  for (auto &worker : this->workers_) {
    worker.join();
  }
}

问:我不明白为什么在析构函数中将destroy_切换为true时需要锁定对象的互斥锁.此外,是否只需要设置其值或是否也需要访问其值?

BQ:可以在保持最初目的的同时改进或优化此线程池实现;一个线程池,可以汇集N个线程并将任务分配给它们以便同时执行?

这个线程池实现从Jakob Progsch’s C++11 thread pool repository开始,通过一个完整的代码步骤来理解其实现背后的目的和一些主观风格的变化.

我将自己介绍给并发编程,还有很多东西需要学习 – 我现在是一名新手并发程序员.如果我的问题措辞不正确,请在您提供的答案中进行适当的更正.此外,如果答案可以面向首次引入并发编程的客户,那么这对我自己和任何其他新手来说都是最好的.

最佳答案 如果ThreadPool对象的拥有线程是唯一以原子方式写入destroy_变量的线程,并且工作线程只是从destroy_变量原子读取,那么不需要互斥锁来保护ThreadPool析构函数中的destroy_变量.通常,当必须发生不能通过平台上的单个原子指令(即,超出原子交换的操作等)完成的原子操作集时,互斥是必要的.话虽这么说,线程池的作者可能试图在destroy_变量上强制某种类型的获取语义而不恢复原子操作(即内存栅栏操作),和/或标志本身的设置不被认为是原子操作(取决于平台)…其他一些选项包括将变量声明为volatile以防止其被缓存等.您可以查看
this thread以获取更多信息.

如果没有某种类型的同步操作,最坏的情况可能会导致由于destroy_变量缓存在线程上而无法完成的工作程序.在具有较弱内存排序模型的平台上,如果您允许存在良性内存竞争条件,则总是有可能…

点赞