1. All threads
share
heap data. This requires you to use proper synchronization primitives
whenever
two threads modify or read the shared data. Sometimes, it is obvious
how
to do so:
char a[1000]
void Modify(int m, int n)
{
a[m + n] = a[m] +
a[n];
// ignore bound checks
}
If two thread will be
executing this function, there is no guarantee that both will perceive
consistent values of the members of the array
a. Therefore, such a statment has to be protected by a mutex that
ensures
the proper execution as follows:
char a[1000]
void Modify(int m, int n)
{
Lock(p);
a[m + n] = a[m] +
a[n];
// ignore bound checks
Unlock(p);
}
where p is a
synchronization
variable.
2. Beware of the hidden data structures! While your own variables on the heap must be protected, it is also necessary to ensure that data structures belonging to the libraries and runtime system also be protected. These data structures are allocated on the heap, but you do not see them. Access to library functions can create situations where two threads may corrupt the data structures because proper synchronization is lacking. For example, two threads calling the memory allocator simultaneously through the malloc() library call might create problems if the data structures of malloc() are not designed for concurrent access. In that case, data structures used to track the free space might be corrupted. The solution is to use a thread-safe version of libc.
For this reason, it is important to compile your program with the -D_POSIX_PTHREAD_SEMANTICS flag, so that the compiler knows to use the "thread safe" (synchronized) versions of libraries.
Note that not all libraries offer thread-safe versions. Some library calls may only be used with single threaded programs. Before using a library call, you should find out whether it supports multithreaded use. In Solaris, you do this by looking at the man page for that library function. The man page will indicate that the call is "Thread Safe" (allows concurrent access by different threads assuming you have compiled with -D_POSIX_PTHREAD_SEMANTICS ), "Safe" (uses a lock to avoid concurrency within the call so that multithreaded programs may safely use it at some performance cost), and "Unsafe" (you can't use the call from a multi-threaded program).
3. Simplicity of the code is also an important factor in ensuring correct operation. Complex pointer manipulations may lead to errors and a runaway pointer may start corrupt the stacks of various threads, and therefore manifesting its presence through a set of incomprehensible bugs. Contrived logic, and multiple recursions may cause the stacks to overflow. Modern computer languages such as Java eliminate pointers altogether, and perform the memory allocation and deallocation by automatic memory management and garbage collection techniques. They simplify the process of writing programs in general, and multithreaded programs in particular. Still, without understanding of all the pitfalls that come with programming multithreaded applications, even the most sophistica ted programmer using the most sophisticated language may fall prey to some truly strange bugs.