10.6 The ACE Condition Variable Classes

I l @ ve RuBoard

Motivation

Condition variables allow threads to coordinate and schedule their processing efficiently . Although condition variables are mandated in the Pthreads standard, some widely used OS platforms, such as Win32 and VxWorks, do not implement condition variables natively. Although condition variables can be emulated using other native synchronization mechanisms, such as semaphores and mutexes , it's unnecessarily wasteful and error-prone for application developers to write these emulations. ACE provides the ACE_Condition_Thread_Mutex condition variable wrapper facade to alleviate this problem.

Class Capabilities

The ACE_Condition_Thread_Mutex uses the Wrapper Facade pattern to guide its encapsulation of process-scoped condition variable semantics. The interface of the ACE_Condition_Thread_Mutex class is shown in Figure 10.3 and the following table outlines its key methods :

Methods Description
wait() Atomically releases the mutex associated with the condition variable and sleeps awaiting a timeout or a subsequent notification from another thread via the signal() or broadcast() methods.
signal() Notifies one thread waiting on the condition variable.
broadcast() Notifies all threads waiting on the condition variable.
Figure 10.3. The ACE_Condition_Thread_Mutex Class Diagram

If the time parameter to ACE_Condition_Thread_Mutex::wait() is non-0 the method will block up to that absolute point of time, which is specified as an ACE_Time_Value . If the time elapses before the condition variable is notified, the method returns -1 to the caller with an ETIME errno , signifying that a timeout has occurred. Regardless of how wait() returns, the mutex associated with the condition variable will always be locked.

Condition variables are more appropriate than mutexes or semaphores when complex condition expressions or scheduling behaviors are needed. For instance, condition variables are often used to implement synchronized message queues that provide "producer/consumer" communication to pass messages between threads. The condition variables block producer threads when a message queue is full and block consumer threads when the queue is empty. Since there's no need to maintain event history, condition variables needn't record when they are signaled.

The ACE_Null_Condition class is a zero-cost implementation that conforms to the ACE_Condition_Thread_Mutex interface. This class is similar to the ACE_Null_Mutex class described in Section 10.3; that is, all its methods are implemented as no-ops in accordance with the Null Object pattern [Woo97]. The ACE_Null_Condition class is presented below:

 class ACE_Null_Condition { public:   ACE_Null_Condition (const ACE_Null_Mutex&, int =0, void* =0) {}   ~ACE_Null_Condition () {}   int remove () { return 0; }   int wait (ACE_Time_Value* =0) const {errno = ETIME; return -1;}   int signal () const { return 0;}   int broadcast () const { return 0; } }; 

The wait() method returns “1 with errno set to ETIME so that ACE_Null_Condition works correctly when used in conjunction with the Strategized Locking pattern for classes like the Message_Queue shown in Section 10.5.

Example

By default, a UI threads mutex implementation is nonrecursive; that is, a thread that owns a mutex can't reacquire the same mutex without deadlocking on itself. [2] Although nonrecursive mutexes are efficient, they are too restrictive in certain circumstances. For example, recursive mutexes are particularly useful for callback-driven C++ frameworks, where the framework event loop performs a callback to user-defined code. Since the user -defined code may subsequently reenter framework code via a method entry point, recursive mutexes help prevent deadlock from occurring on mutexes held by the same thread within the framework during the callback.

[2] Note that Pthreads and Win32 provide recursive mutexes natively.

To address these types of scenarios, ACE provides support for process-scoped recursive mutexes via the ACE_Recursive_Thread_Mutex class. The interface for this class is shown in Figure 10.4. The public interface of this class conforms to the ACE_LOCK* pseudo-class in Figure 10.1 on page 209. The following code illustrates how an ACE_Condition_Thread_Mutex can be used to implement recursive mutex semantics on platforms that don't support them natively.

Figure 10.4. The ACE_Recursive_Thread_Mutex Class Diagram

The nesting_level_ keeps track of the number of times a thread with owner_id_ has acquired the recursive mutex. The lock_ serializes access to the nesting_level_ and owner_id_ . The lock_available_ condition variable is used to suspend nonowner threads that are waiting for the nesting level to drop to 0 and the mutex to be released. The ACE_Recursive_Thread_Mutex constructor initializes these data members :

 ACE_Recursive_Thread_Mutex::ACE_Recursive_Thread_Mutex   (const char *name, void *arg)   : nesting_level_ (0), owner_id_ (0), lock_ (name, arg),     // Initialize the condition variable.     lock_available_ (lock_, name, arg) {} 

We next show how to acquire a recursive mutex.

 1 int ACE_Recursive_Thread_Mutex::acquire ()  2 {  3   ACE_thread_t t_id = ACE_OS::thr_self ();  4  5   ACE_GUARD_RETURN (ACE_Thread_Mutex, guard, lock_, -1);  6  7   if (nesting_level_ == 0) {  8     owner_id_ = t_id;  9     nesting_level_ = 1; 10   } 11   else if (t_id == owner_id_) 12     nesting_level_++; 13   else { 14     while (nesting_level_ > 0) 15       lock_available_.wait (); 16 17     owner_id_ = t_id; 18     nesting_level_ = 1; 19   } 20   return 0; 21 } 

Lines 3 “5 We start by determining which thread we're called from and then acquire the lock_ mutex using the Scoped Locking idiom.

Lines 7 “12 If there's no contention , assume mutex ownership immediately. Otherwise, if we own the mutex already, just increment nesting level and proceed to avoid self-deadlock.

Lines 13 “20 If the mutex is owned by another thread, we use the condition variable lock_available_ to wait for the nesting level to drop to zero. When the wait() method is invoked on lock_available_ it atomically releases the lock_ mutex and puts the calling thread to sleep. When the nesting level finally drops to zero, we can acquire the lock_ and assume ownership of the recursive mutex. The destructor of guard releases the lock_ via the Scoped Locking idiom.

We show the ACE_Recursive_Thread_Mutex::release () method next:

 int ACE Recursive Thread _Mutex::release () {   ACE_thread_t t_id = ACE_OS::thr_self ();   // Automatically acquire mutex.   ACE_GUARD_RETURN (ACE_Thread_Mutex, guard, lock_, -1);   nesting_level_--;   if (nesting_level_  == 0)     lock_available_.signal  (); // Inform waiters the lock is free.   return 0; // Destructor of <guard> releases the <lock_>. } 

When the signal() method is invoked on lock_available_ , it wakes up one of the threads waiting for the condition to be signaled.

I l @ ve RuBoard


C++ Network Programming
C++ Network Programming, Volume I: Mastering Complexity with ACE and Patterns
ISBN: 0201604647
EAN: 2147483647
Year: 2001
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net