【问题标题】:Terminate workers终止工人
【发布时间】: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


【解决方案1】:

C++ 中确实存在 try finally 等效项,但不在核心语言中。它被称为 ScopeGuard,最初由 Andrej Alexandrescu 创造,他是 C++ 领域的“摇滚明星”之一。在这里,他在 2012 年 C++ 和 Beyond 会议上介绍了新版本http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C

更重要的是这里的代码: https://gist.github.com/KindDragon/4650442

你的例子做了几乎同样的事情。如果您希望其他 C++ 程序员理解您的意思,您可能应该将其称为 ScopeGuard 或使用 Alexandrescus 代码(您可能应该将其放入库或公共包含中,它经常使用)。 C++ 程序员对所有事情都使用 RAII,在我看来,表达意图很重要,这由

SCOPE_EXIT {
    std::unique_lock<std::mutex> lk(mtx_);
    done_ = true;
    beginWork_.notify_all();
};

在你的情况下。

我工作的地方 ScopeGuard 被认为是很好的风格,并且很好地通过了代码审查。如果您想在商业上使用它,它也是公共领域。

【讨论】:

    【解决方案2】:

    C++ 方法是使用名为RAII 的东西。您使用总是调用析构函数的事实来确保始终运行某些代码。

    【讨论】:

    • 当我在其他语言中找到一些习语时,我使用 RAII 对它们进行建模,并想知道我是否做对了。您会注意到,我上面的示例确实使用析构函数来做一些事情,但是我很难描述在这种 RAII 案例中的“资源”实际上是什么,或者它是从哪里获取的。所以我使用的是 RAII 的实现技术,而不是它背后的建模思想。
    猜你喜欢
    • 2020-03-22
    • 2023-03-25
    • 2021-08-10
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多