More Mutex and CRITICAL_SECTION Guidelines
We are now familiar with all the Windows synchronization objects and have explored their utility in the examples. Mutexes and CSs were the first objects described and, because events will be used extensively in the next chapter, it is worthwhile to conclude this chapter with some guidelines for using mutexes and CSs to help ensure program correctness, maintainability, and performance.Nearly everything is stated in terms of mutexes; the statements also apply to CSs unless noted otherwise.
- If there is no time-out associated with WaitForSingleObject on a mutex handle, the calling thread could block forever. It is the programmer's responsibility to ensure that an owned (or locked) mutex is eventually unlocked.
- If a thread terminates, or is terminated, before it leaves (unlocks) a CS, the CS remains locked. Mutexes have the very useful abandonment property.
- If WaitForSingleObject times out waiting for a mutex, do not access the resources that the mutex is designed to protect.
- There may be multiple threads waiting on a given locked mutex. When the mutex is unlocked, exactly one of the waiting threads is given mutex ownership and moved to the ready state by the OS scheduler based on priority and scheduling policy. Do not assume that any particular thread will have priority; as always, program so that your application will operate correctly regardless of which waiting thread gains mutex ownership and resumes execution. The same comment applies to threads waiting on an event; do not assume that a specific thread will be the one released when the event is signaled or that threads will be unblocked in any specific order.
- A code critical section is everything between the points where the thread gains and relinquishes mutex ownership. A single mutex can be used to define several critical sections. If properly implemented, at most one thread can execute a mutex's critical section at any time.
- Mutex granularity affects performance and is an important consideration. Each critical section should be just as long as necessary, and no longer, and a mutex should be owned just as long as necessary, and no longer. Large critical sections, held for a long period of time, defeat concurrency and can impact performance.Program 8-1 and 8-2 use this technique.)
- Document the invariant as precisely as possible, in words or even as a logical, or Boolean, expression. The invariant is a property of the protected resource that you guarantee holds outside the critical code section. An invariant might be of the form: "the element is in both or neither list," "the checksum on the data buffer is valid," "the linked list is valid," or "0 <= nLost + nCons <= sequence." A precisely formulated invariant can be used with the ASSERT macro when debugging a program, although the ASSERT statement should be in its own critical code section.
- Ensure that each critical section has exactly one entrance, where the thread locks the mutex, and exactly one exit, where the thread unlocks the mutex. Avoid complex conditional code and avoid premature exits, such as break, return, and goto statements, from within the critical section. Termination handlers are useful for protecting against such problems.
- If the critical section code becomes too lengthy (longer than one page, perhaps), but all the logic is required, consider putting the code in a function so that the synchronization can be easily comprehended. For example, the code to delete a node from a balanced search tree while the tree is locked might best be put in a function.