Thread safety is a computer programming concept applicable in the context of multi-threaded programs. A piece of code is thread-safe if it only manipulates shared data structures in a manner that guarantees safe execution by multiple threads at the same time. There are various strategies for making thread-safe data structures [1].
Software libraries can provide certain thread-safety guarantees. For example, concurrent reads might be guaranteed to be thread-safe, but concurrent writes might not be. Whether or not a program using such a library is thread-safe depends on whether it uses the library in a manner consistent with those guarantees.
A key challenge in multi-threaded programming, thread safety was not a concern for most application developers until the 1990s when operating systems began to expose multiple threads for code execution. Today, a program may execute code on several threads simultaneously in a shared address space where each of those threads have access to virtually all of the memory of every other thread. Thus the flow of control and the sequence of accesses to data often have little relation to what would be reasonably expected by looking at the text of the program, violating the principle of least astonishment. Thread safety is a property that allows code to run in multi-threaded environments by re-establishing some of the correspondences between the actual flow of control and the text of the program, by means of synchronization.
Contents |
It is not easy to determine whether or not a piece of code is thread-safe. However, there are several indicators that suggest the need for careful examination to see if it is unsafe:
All this can be subsumed under manipulation of global state, which is not neccessarily restricted to a single program/process.
There are a few ways to achieve thread safety:
In the following piece of C code, the function is thread-safe, but not reentrant:
int function() { mutex_lock(); ... function body ... mutex_unlock(); }
In the above, function
can be called by different threads without any problem. But if the function is used in a reentrant interrupt handler and a second interrupt arises inside the function, the second routine will hang forever. As interrupt servicing can disable other interrupts, the whole system could suffer.
Note that a piece of code can be thread safe, and yet unable to run at the same time that some other piece of code is running. A trivial example of that is when that other piece of code restarts the computer. The following piece of C code, presents a less obvious situation where a thread is using a file that another thread or process might delete.
int function() { char *filename = "/etc/config"; FILE *config; if (file_exist(filename)){ config = fopen(filename); } }
In the above, the function is thread-safe, as it can be called from any number of threads and will not fail. But all the calls should be in a controlled environment. If executed in a multi-process environment, or if the file is stored on a network-shared drive, there is no warranty that it won't be deleted.
One approach to making data thread-safe that combines several of the above elements is to make changes atomically to update the shared data. Thus, most of the code is concurrent, and little time is spent serialized.