10.4 The ACE ReadersWriter Lock Classes

I l @ ve RuBoard

10.4 The ACE Readers/Writer Lock Classes

Motivation

Readers/writer locks allow efficient concurrent access to resources whose contents are searched much more often than they are changed. Many operating systems support readers/writer semantics in their file-locking APIs. Involving the file system in synchronization activities is unnecessarily inefficient, however, and can block under unpredictable situations, such as when using network-mounted storage for the lock file. Moreover, file-locking mechanisms work only at the system-scope level, rather than at process scope.

Surprisingly few operating systems provide readers/writer lock implementations at the process-scope level. For example, Pthreads (without UNIX98 extensions), Win32 threads, and many real-time operating systems don't support them natively. UI threads support readers/writer locks via the rwlock_t type. Addressing these portability variations in each application is tedious and error prone, which is why ACE provides the readers/writer lock wrapper facades.

Class Capabilities

ACE encapsulates the native OS readers/writer lock mechanisms with the ACE_RW_Thread_Mutex and ACE_RW_Process_Mutex classes. These classes apply the Wrapper Facade pattern to implement the semantics of process- and system-scoped readers/writer locks portably. The interface for these classes is identical to the signatures of ACE_LOCK* pseudo-class shown in shown in Figure 10.1 on page 209.

If a platform supports process-scoped readers/writer locks, the ACE_RW_Thread_Mutex class simply encapsulates the native synchronization variable. On OS platforms that lack native readers/writer locks, however, ACE provides readers/writer implementations using existing low-level synchronization mechanisms, such as mutexes , condition variables , or semaphores. The ACE readers/writer implementation gives preference to writers. Thus, if there are multiple readers and a single writer waiting on the lock, the writer will acquire it first.

Example

Although the ACE_Guard -based implementation of handle_data() in the example on page 216 solved several problems, it still required obtrusive code changes, that is, adding the ACE_GUARD_RETURN inside the loop. A less error prone and obtrusive solution leverages

  • C++ operator overloading

  • ACE_RW_Thread_Mutex

  • The Scoped Locking idiom, through use of the ACE_WRITE_GUARD and ACE_READ_GUARD macros

We use these capabilities below to create an Atomic_Op class that supports thread-safe arithmetic operations:

 class Atomic_op { public:   // Initialize <count_> to <count>.   Atomic_op (long count = 0)     : count_ (count) {}   // Atomically pre-increment <count_>.   long operator++ () {     // Use the <acquire_write> method to acquire a write lock.     ACE_WRITE_GUARD_RETURN (ACE_RW_Thread_Mutex, guard, lock_,                             -1);     return ++count_;   }   // Atomically return <count_>.   operator long () {     // Use the <acquire_read> method to acquire a read lock.     ACE_READ_GUARD_RETURN (ACE_RW_Thread_Mutex, guard, lock_,                            0);     return count_;   }   // ... Other arithmetic operators omitted. private:   // Readers/writer lock.   ACE_RW_Thread_Mutex lock_;   // Value of the <Atomic_Op> count.   long count_; }; 

The Atomic_Op class overloads the standard arithmetic operators, that is, ++, --, +=, etc., on long data types. Since these methods modify the count_ they achieve exclusive access by acquiring a write lock. In contrast, a read lock will suffice for the operator long() since it allows multiple threads to read the count_ concurrently.

By applying the Atomic_Op class, we can now write the following code, which is almost identical to the original nonthread-safe code. Only the typedef of COUNTER has changed:

 typedef Atomic_Op COUNTER; static COUNTER request_count; // File scope global variable.   virtual int handle_data (ACE_SOCK_Stream *) {     while (logging_handler_.log_record () != -1)     // Keep track of number of requests.     ++request_count; // Actually calls <Atomic_Op::operator++>.     ACE_DEBUG ((LM_DEBUG, "request_count = %d\n",                // Actually calls <Atomic_Op::operator long>.                (long) request_count));   } 

The calls to both operator++() and operator long() use the acquire_write() and acquire_read() methods on the ACE_RW_Thread_Mutex , respectively. Thus, arithmetic operations on objects of instantiated Atomic_Op classes now increment/decrement counters correctly on a multiprocessor, without changing much existing application code!

The Atomic_Op class shown above is actually a simplification of the ACE_Atomic_Op class template, a portion of which is defined below:

 template <class TYPE, class LOCK> class ACE_Atomic_Op { public:   //... Constructors and arithmetic operators omitted. private:   LOCK lock_;   TYPE type_; }; 

The ACE_Atomic_Op produces a simple, yet remarkably expressive parameterized class abstraction by combining

  • The Scoped Locking idiom and Strategized Locking pattern with

  • The use of C++ templates and operator overloading.

This template class operates correctly and atomically on the family of types requiring atomic operations. To provide the same thread-safe functionality for other arithmetic types, for example, we can simply instantiate new objects of the ACE_Atomic_Op template class as follows :

 ACE_Atomic_Op<double, ACE_Null_Mutex> double_counter; ACE_Atomic_Op<Complex, ACE_RW_Thread_Mutex> complex_counter; 
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