6.5 Limitations with OS Concurrency Mechanisms

I l @ ve RuBoard

Developing networked applications using the native OS concurrency mechanisms outlined above can cause portability and reliability problems. To illustrate some of these problems, consider a function that uses a UI threads mutex to solve the auto-increment serialization problem we observed with request_count on page 131 in Section 6.4:

 typedef u_long COUNTER; static COUNTER request_count; // File scope  global variable. static mutex_t m; // Protects request count (initialized to zero). // ...   virtual int handle_data (ACE_SOCK_Stream *) {     while (logging_handler_.log_record () != -1) {       // Keep track of number of requests.       mutex_lock (&m);   // Acquire lock       ++request_count;   // Count # of requests       mutex unlock (&m); // Release lock     }     mutex_lock (&m);     int count = request_count;     mutex_unlock (&m);     ACE_DEBUG ((LM_DEBUG, "request_count = %d\n", count));     logging_handler_.close ();     return 0;   } 

In the code above, m is a variable of type mutex_t , which is automatically initialized to 0. In UI threads, any synchronization variable that's set to zero is initialized implicitly with its default semantics [EKB + 92]. For example, the mutex_t variable m is a static variable that's initialized by default in the unlocked state. The first time the mutex_lock() function is called, it will therefore acquire ownership of the lock. Any other thread that attempts to acquire the lock must wait until the thread owning lock m releases it.

Although the code above solves the original synchronization problem, it suffers from the following drawbacks:

  • Obtrusive. The solution requires changing the source code to add the mutex and its associated C functions. When developing a large software system, making these types of modifications manually will cause maintenance problems if changes aren't made consistently.

  • Error-prone. Although the handle_data() method is relatively simple, it's easy for programmers to forget to call mutex_unlock() in more complex methods . Omitting this call will cause starvation for other threads that are blocked trying to acquire the mutex. Moreover, since a mutex_t is nonrecursive, deadlock will occur if the thread that owns mutex m tries to reacquire it before releasing it. In addition, we neglected to check the return value of mutex_lock() to make sure it succeeded, which can yield subtle problems in production applications.

  • Unseen side-effects. It's also possible that a programmer will forget to initialize the mutex variable. As mentioned above, a static mutex_t variable is implicitly initialized on UI threads. No such guarantees are made, however, for mutex_t fields in objects allocated dynamically. Moreover, other OS thread implementations , such as Pthreads and Win32 threads, don't support these implicit initialization semantics; that is, all synchronization objects must be initialized explicitly.

  • Non-portable. This code will work only with the UI threads synchronization mechanisms. Porting the handle_data() method to use Pthreads and Win32 threads will therefore require changing the locking code to use different synchronization APIs altogether.

In general, native OS concurrency APIs exhibit many of the same types of problems associated with the Socket API in Section 2.3. In addition, concurrency APIs are even less standardized across OS platforms. Even where the APIs are similar, there are often subtle syntactic and semantic variations from one implementation to another. For example, functions in different drafts of the Pthreads standard implemented by different operating system providers have different parameter lists and return different error indicators.

Sidebar 13 outlines some of the differences between error propagation strategies for different concurrency APIs. This lack of portability increases the accidental complexity of concurrent networked applications. Therefore, it's important to design higher-level programming abstractions, such as those in ACE, to help developers avoid problems with nonportable and nonuniform APIs.

Sidebar 13: Concurrency API Error Propagation Strategies

Different concurrency APIs report errors to callers differently, For example, some APIs, such as UI threads and Pthreads, return 0 on success and a non-0 error value on failure. Other APIs, such as Win32 threads, return 0 on failure and indicate the error value via thread-specific storage. This diversity of behaviors is confusing and nonportable. In contrast, the ACE concurrency wrapper facades define and enforce a uniform approach that always returns -1 if a failure occurs and sets a thread-specific errno value to indicate the cause of the failure.

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