[英]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.