Boost logo

Boost :

Subject: Re: [boost] [thread] countdown_latch
From: Michael Marcin (mike.marcin_at_[hidden])
Date: 2013-04-21 14:06:12


On 4/21/2013 8:55 AM, Vicente J. Botet Escriba wrote:
> Le 21/04/13 13:18, Michael Marcin a écrit :
>> On 4/21/2013 5:45 AM, Vicente J. Botet Escriba wrote:
>>> Le 21/04/13 10:27, Michael Marcin a écrit :
>>>> I recently ran across the need to spawn a thread and wait for it to
>>>> finish its setup before continuing.
>>>>
>>>> The accepted answer seems to be using a mutex and condition variable
>>>> to achieve this.
>>>>
>>>> However that work clutters up the code quite a bit with the
>>>> implementation details.
>>>>
>>>> I came across Java's CountDownLatch which does basically the same work
>>>> but bundles it up into a tidy package.
>>>>
>>>>
>>> What about using boost::barrier [1]?
>>>
>>> Best,
>>> Vicente
>>>
>>
>> Ah didn't know that existed. Looks like it serves a similar purpose.
>>
>> Still there are a few differences.
>>
>> Looks like barrier is a bit fatter to let it handle restarting on a
>> new generation.
>>
>> Usage requires you to know the number of people that are going to call
>> wait at construction time as well.
>>
>> IF you replaced the countdown_latch with barrier in my example you
>> introduce more synchronization than is necessary.
>> In addition to the constructor waiting on thread_func now thread_func
>> must wait for the constructor.
>>
>>
>>
> You are right. After further analysis the split of the wait()
> count_down() of the latch class could be complementary to the current
> boost::barrier class.
>
> The difference been that with a latch the thread will not block on (2)
> while using a barrier would synchronize all the threads in (1) and (2)
> as the barrier::wait() is equivalent to a latch::count_down() and
> latch::wait()
>
> There is a C++1y proposal [1] that propose having two classes. However
> having two classes that are really quite close seems not desirable. The
> problem is that boost::barrier use already a wait() function that do the
> count down and synchronize up to the count is zero.
>
> I guess that the better would be to define a new set of latch classes
> that provides wait/count_down/count_down_and_wait. The current barrier
> class could be deprecated once the new classes are ready.
>
> [1] adds the possibility to reset the counter. This doesn't seems to add
> any complexity to the basic latch
>
> [1] adds the possibility to set a function that is called when the
> counter reach the value 0. This is in my opinion useful but would need
> an additional class. I don't think the names latch and barrier would be
> the good ones if the single difference is to be able to set this function.
>
> boost::barrier auto reset itself once the counter reaches the value zero.
>
> I would propose 2/3 classes
>
> Basic Latch respond to your needs and adds some more function that don't
> make the implementation less efficient.
>
> class latch
> {
> public:
> latch( latch const&) = delete;
> latch& operator=( latch const&) = delete;
>
> /// Constructs a latch with a given count.
> latch( std::size_t count );
>
> /// Blocks until the latch has counted down to zero.
> void wait();
>
> bool try_wait();
>
> template <class Rep, class Period>
> cv_statuswait_for( const chrono::duration<Rep, Period>& rel_time );
> template <class lock_type, class Clock, class Duration>
> cv_status wait_until( const chrono::time_point<Clock, Duration>&
> abs_time );
>
> /// Decrement the count and notify anyone waiting if we reach zero.
> /// @Requires count must be greater than 0
> void count_down();
>
> /// Decrement the count and notify anyone waiting if we reach zero.
> /// Blocks until the latch has counted down to zero.
> /// @Requires count must be greater than 0
> void count_down_and_wait();
>
> /// Reset the counter
> /// #Requires This method may only be invoked when there are no
> other threads currently inside the|count_down_and_wait()| method.
>
> void reset(std::size_t count_ );
>
>
> };
>
>
> A completion latch has in addition to its internal counter a completion
> function that will be invoked when the counter reaches zero.
> The completion function is any nullary function returning nothing.
>
> class completion_latch
> {
> public:
> typedef 'implementation defined' completion_function;
> static const completion_function noop;
>
> completion_latch( completion_latch const& ) = delete;
> completion_latch& operator=( completion_latch const& ) = delete;
>
> /// Constructs a latch with a given count and a noop completion
> function.
> completion_latch( std::size_t count);
>
> /// Constructs a latch with a given count and a completion function.
> template <typename F>
> completion_latch( std::size_t count, F&& fct);
>
> /// Blocks until the latch has counted down to zero.
> void wait();
> bool try_wait();
> template <class Rep, class Period>
> cv_status wait_for( const chrono::duration<Rep, Period>& rel_time );
> template <class lock_type, class Clock, class Duration>
> cv_status wait_until( const chrono::time_point<Clock, Duration>&
> abs_time );
>
> /// Decrement the count and notify anyone waiting if we reach zero.
> /// @Requires count must be greater than 0 or undefined behavior
> void count_down();
>
> /// Decrement the count and notify anyone waiting if we reach zero.
> /// Blocks until the latch has counted down to zero.
> /// @Requires count must be greater than 0
> void count_down_and_wait();
>
> /// Reset the counter with a new value for the initial count.
> /// #Requires This method may only be invoked when there are no
> other threads
> /// currently inside the count_down and wait related functions.
> /// It may also be invoked from within the registered completion
> function.
>
> void reset( std::size_t count );
>
> /// Resets the latch with the new completion function.
> /// The next time the internal count reaches 0, this function will
> be invoked.
> /// #Requires This method may only be invoked when there are no
> other threads
> /// currently inside the count_down and wait related functions.
> /// It may also be invoked from within the registered completion
> function.
> /// Returns the old completion function if any or noop if
> template typename F>
> completion_function then(F&&);
>
> };
>
>
> Optionally we could add a Cyclic latch provides the same interface than
> latch but that reset itself when zero is reached (as boost::barrier).
> This would be more efficient than been forced to add a completion
> function that reset the counter.
>
> What do you think of these interfaces?
>
> Best,
> Vicente
>
> [1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3600.html
>

I'm not a threading guru so forgive me if I'm totally off.

/// Resets the latch with the new completion function.
/// The next time the internal count reaches 0, this function will be
invoked.
/// #Requires This method may only be invoked when there are no other
threads
/// currently inside the count_down and wait related functions.
/// It may also be invoked from within the registered completion function.
/// Returns the old completion function if any or noop if

How is it that this function can be invoked with no threads inside
count_down and wait related functions?

bool count_down_and_wait()
{
   boost::unique_lock<boost::mutex> lock(m_mutex);
   unsigned int gen = m_generation;

   if (--m_count == 0)
   {
     m_generation++;
1) completion_func(); < waiters are stil waiting
     m_cond.notify_all();
2) completion_func(); < waiters are blocked trying to acquire the lock

     lock.unlock();
3) completion_func(); < waiters could be all gone, but I don't think
this guaranteed
     return true;
   }

   while (gen == m_generation)
     m_cond.wait(lock);
   return false;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk