【发布时间】:2013-03-05 11:21:55
【问题描述】:
我有一个启动多个工作线程的函数。每个工作线程都被一个对象封装,该对象的析构函数会尝试join线程,即调用if (thrd_.joinable()) thrd_.join();。但是每个工人必须完成多少工作是先验未知的。管理功能使用互斥锁和条件变量将工作单元分配给线程。如果没有更多工作要做,则在持有互斥锁的同时设置某个标志,然后通知所有在条件变量上阻塞的线程,以便它们醒来,注意到更改的标志,然后关闭。
即使主线程中出现异常,我也希望此关闭工作。在Java 中,我会使用finally 子句总是 设置标志并在工作处理循环结束时通知线程。作为C++ doesn't have finally,我写了自己的替换:
class FinallyGuard {
private:
std::function<void()> f_;
public:
FinallyGuard(std::function<void()> f) : f_(f) { }
~FinallyGuard() { f_(); }
};
void Manager::manageWork(unsigned NumWorkers) {
// Order matters: destructors are called in reverse order and will
// 1. release the mutex lock so that workers can proceed
// 2. unblock all threads using the finally workalike
// 3. destroy the workers and join their threads
std::forward_list<Worker> workers;
FinallyGuard signalEndGuard([this] {
std::unique_lock<std::mutex> lk(mtx_);
done_ = true;
beginWork_.notify_all();
});
std::unique_lock<std::mutex> lk(mtx_);
for (unsigned i = 0; i != numWorkers; ++i)
workers.emplace_front(this);
while (haveMoreWork()) {
// …
}
}
但我显然在考虑其他语言的概念。 是否有更类似于 C++ 的方法来实现这一点? 解决方案要么需要执行一些代码,以实现方法的正常返回和抛出异常的情况,要么提供一些更好的机制唤醒工人而不是标志和条件变量的组合。
【问题讨论】:
-
为什么不使用可以放置已完成工作的队列,并且不要阻塞任何线程。然后你可以定期检查你的队列,看看它是否包含你需要的位,然后继续。
-
看来
FinallyGuard(我可能会称之为on_scope_exit(),但这是个人喜好问题)对我来说还可以。但是,请注意,每次退出manageWork()的范围时都会调用它,因此即使对于正常的returns,也不仅仅是因为抛出了异常。 -
@AndyProwl 这是
try...finally语句的常用语义。 -
@TonyTheLion:完成的工作不是问题,因为结果已经存储在共享内存中。这是我正在等待的新工作单位。经理一次创建一个。这是我的用例之一,它只能在所有工作人员完成之前的工作后才能这样做,因此预先生成所有工作单元不适用于这种情况。
-
@AndyProwl:确实,我依靠 dtor 来终止我的线程,以实现函数的正常结束和异常结束。在正常操作期间使用该代码路径使我更加乐观地认为它也可以在罕见的例外情况下工作,即使在我不想为此编写单元测试的情况下也是如此。
标签: c++ multithreading c++11 finally