【发布时间】:2015-05-24 13:33:46
【问题描述】:
考虑以下用 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's C++11 thread pool repository 派生的,通过完整的代码逐步了解其实现背后的目的和一些主观风格的变化。
我正在向自己介绍并发编程,还有很多东西要学——就目前而言,我是一个新手并发程序员。如果我的问题措辞不正确,请在您提供的答案中进行适当的更正。此外,如果答案可以针对第一次接触并发编程的客户,那将是最好的——对我自己和任何其他新手也是如此。
【问题讨论】:
-
销毁标志是谓词的一部分。条件变量/互斥体/谓词协议的规则#1:永远不要更改,甚至检查,谓词数据,除非受到保护它的互斥体的保护。请记住,互斥锁保护谓词数据;不是条件变量。如果破坏标志可以被原子地修改,它将被豁免。如图所示,不是。显示的代码是正确的,虽然它可能看起来有点矫枉过正,但却是学习的正确模式。
-
我认为这里的主要困惑是,对于真正的同步内存栅栏或原子读/写操作来说,互斥锁通常是不必要的繁重操作,这两者都可以作为 C 的一部分使用++11 标准...恕我直言,使用互斥锁是在原子之前防止良性数据竞争的“安全”方式,但现在不一定是最佳方式...
-
我向任何学习 C++ 并发编程的人推荐 Anthony Williams 的书C++ Concurrency In Action,它涵盖了 C++11 并发,包括线程和原子库、C++内存模型等。在 9.1 节中有一个简单线程池的代码。其中等价的变量是std::atomic_bool done,析构函数只是设置done=true,让编译器决定是否需要存储到内存之外的任何东西(它不在 x86 上,因为它的 Total Store Order 内存模型)。见stackoverflow.com/questions/388242/…
标签: c++ multithreading thread-safety threadpool c++14