0

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.

Community
  • 1
  • 1
MvG
  • 57,380
  • 22
  • 148
  • 276
  • Why don't use a queue that you can put finished work on, and don't block any threads. Then you can check your queue periodically and see if it contains the bits you need, and move on. – Tony The Lion Mar 05 '13 at 11:26
  • 1
    It seems the `FinallyGuard` (which I would probably call `on_scope_exit()`, but that's a matter of taste) is OK to me. However, be aware this will be called every time the scope of `manageWork()` is exited, so even for normal `return`s, not just because of an exception being thrown. – Andy Prowl Mar 05 '13 at 11:28
  • @AndyProwl that's the usual semantics of a `try...finally` statement. – didierc Mar 05 '13 at 12:23
  • @TonyTheLion: Finished work isn't an issue, as results are stored in shared memory already. It's new work units I'm waiting on. The manager creates them one at a time. It one of my use cases, it can do so only after all workers have finished their previous bunch of work, so generating all work units up front won't work for that case. – MvG Mar 05 '13 at 12:25
  • @AndyProwl: Indeed I'm relying on the dtor to terminate my threads both for normal and exceptional end of the function. Having that code path used during normal operations makes me more optimistic that it will work in the rare exceptional case as well, even in cases where I don't feel like writing a unit test for this. – MvG Mar 05 '13 at 12:27
  • @MvG you can use a condition variable that is signalled when a thread becomes available to take on new work. What's the problem with that? – Tony The Lion Mar 05 '13 at 12:27
  • @TonyTheLion: The `beginWork_` member in my code is just that. The problem is where to write the code so that it gets executed both when the function exits normally and when the function throws an exception. – MvG Mar 05 '13 at 12:31
  • did you have a look at [this question](http://stackoverflow.com/questions/500244/is-there-a-favored-idiom-for-mimicing-javas-try-finally-in-c)? – didierc Mar 05 '13 at 12:46
  • also, why not put `workers` directly in `Manager` attributes, and have it do the clean up in its destructor? – didierc Mar 05 '13 at 12:52
  • ah, maybe you want to keep track of the fact that an exception occured. – didierc Mar 05 '13 at 12:53
  • @didierc: read [that question](http://stackoverflow.com/questions/500244/is-there-a-favored-idiom-for-mimicing-javas-try-finally-in-c) and concluded that I'll have to use destructors for my approach to work. Then wrote a simple helper class to perform this specific task, given references to the mutex, the flag and the condition variable. Then thought some more about lambdas and decided that I like that generic solution a lot better, since I consider it easier to read. – MvG Mar 05 '13 at 13:01
  • @didierc: Using attributes instead of local variables won't make much of a difference except in terms of lifetime. I still need that `notify_all` before I `join` the threads. I could do the `notify_all` in the `Worker` destructor, if that is what you meant, but that would feel semantically incorrect, and would cause a number of redundant notifications as well. – MvG Mar 05 '13 at 13:02
  • sorry, I don't fully grasp the content of your code, maybe that's why my suggestion was flawed. – didierc Mar 05 '13 at 13:41

2 Answers2

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.

odinthenerd
  • 5,422
  • 1
  • 32
  • 61
0

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.

doron
  • 27,972
  • 12
  • 65
  • 103
  • And I use RAII to model some idioms as I find them in other languages, and wonder whether I'm doing the right thing. As you'll notice, my example above does use the destructor to do stuff, but I'd be hard pressed to describe what the “resource” in this case of RAII actually is, or where exactly it was aquired. So I'm using the implementation technique of RAII, but not really the modelling idea behind it. – MvG Mar 05 '13 at 17:10