|
The biggest conceptual difference between sequential and multithreaded programs is the way the programmer should view memory. In a sequential program, memory can be thought of as being stable unless the program is actively modifying it. For a multithreaded program, however, it is better to think of all memory as spinning (being changed by other threads) unless the programmer does something explicit to freeze (or stabilize) it.
All memory that a thread accesses should be frozen before use, this happens naturally for memory that is read-only or is never given out to more than one thread (for example, local variables). For read and write, thread-shared memory, the only way to freeze memory is to use locks. It is not sufficient, however, just to take a lock., it is necessary that all the code that accesses a particular memory location take the same lock., only then will holding the lock ensure that no other thread is concurrently modifying the location.
The protocol gives the programmer a lot of freedom in making two key design decisions. The first is how much memory is protected by a particular lock and, as a result, how many locks a program needs to cover all thread-shared, read/write memory. The second is how long the locks are held. The sooner a lock is released, the sooner another thread can use the memory protected by that lock. However, once a lock is released, its value is no longer stable and care must be taken to ensure this does not break the algorithm.
There is a spectrum of possible design choices. On one extreme, all thread-shared, read and write memory could be protected by a single lock that is held for large units of work.. This design is likely to be race-free, but affords relatively few opportunities for multiple threads to run concurrently. On the other extreme, every memory location could have its own independent lock, and each lock is only held for the mininum time needed to update that location. This design allows for more concurrency but is more complex, significantly more error prone, and increases lock overhead (since locks are entered and released often).
It is at this latter extreme where lock-free techniques become applicable. Conceptually, if the time a lock needs to be held can be shrunk to a single memory access, then it is possible to take advantage of the hardware to avoid some or all of the locking overhead. Unfortunately, having many small regions protected by different locks is the most error prone part of the design spectrum.
|