Terminate workers

2019-09-07 15:28发布

问题:

I've got a function which starts a number of worker threads. Each worker thread is encapsulated by an object, and the destructor of that object will try to join the thread, i.e calls if (thrd_.joinable()) thrd_.join();. But it is unknown a priori how much work each worker will have to perform. the managing function assigns work units to the threads using a mutex and a condition variable. If there is no more work to do, a certain flag is set while the mutex is being held, and then all threads blocked on the condition variable are notified so they wake up, notice the changed flag, and shut down.

I want this shutdown to work even if there is an exception in the main thread. In Java I'd use a finally clause to always set the flag and notify threads at the end of the work processing loop. As C++ doesn't have finally, I wrote my own replacement:

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()) {
    // …
  }
}

But I'm clearly thinking in concepts from other languages here. Is there a more C++-like way to achieve this? A solution would either need to execute some code both for normal return from a method and for the case where an exception is thrown, or provide some better mechanism to wake up workers instead of the flag and condition variable combination.

回答1:

A try finally equivalent does exist in C++, although not in the core language. It is called a ScopeGuard origionally created by Andrej Alexandrescu one of the "rock stars" of the C++ field. Here he presents the new version at C++ and Beyond conference 2012 http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C

and more importantly here is the code: https://gist.github.com/KindDragon/4650442

Your example does pretty much the same thing. You probably should call it a ScopeGuard or use Alexandrescus code (which you should probably put in a library or common include, its used often) if you want other C++ programmers to understand what you mean. C++ Programmers use RAII for everything, in my mind its important to express intent which is done quite nicely by

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

in your case.

Where I work ScopeGuard is considered good style and passes code review just fine. It is also public domain in case you want to use it commercially.



回答2:

The C++ way is to use something called RAII. You use the fact that destructors are always called to ensure that some code is always run.