繁体   English   中英

安全地销毁线程池

[英]Safely Destroying a Thread Pool

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

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

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

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

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

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

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的C ++ 11线程池存储库中分离出来的,其中包含一个完整的代码步骤,以了解其实现背后的目的和一些主观样式更改。

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

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

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

C ++将数据争用定义为可能同时访问对象的多个线程,其中至少一个访问是写入。 具有数据争用的程序具有未定义的行为。 如果你在没有持有互斥锁的情况下写你的析构函数中的destroy ,你的程序将会有未定义的行为,我们无法预测会发生什么。

如果你在没有持有互斥锁的情况下阅读其他地方的destroy ,那么当析构函数写入它时也可能发生这种读取,这也是数据竞争。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM