I l @ ve RuBoard |
10.4 The ACE Readers/Writer Lock ClassesMotivationReaders/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 CapabilitiesACE 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. ExampleAlthough 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
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
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 |