Thread safety

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

Identification

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.

Implementation

There are a few ways to achieve thread safety:

Re-entrancy 
Writing code in such a way that it can be partially executed by a thread, reexecuted by the same thread or simultaneously executed by another thread and still correctly complete the original execution. This requires the saving of state information in variables local to each execution, usually on a stack, instead of in static or global variables or other non-local state. There are still some rare cases where non-local state can be used in a reentrant function, if the access is done through atomic operations and the data-structure is re-entrancy safe.
Mutual exclusion or Process synchronization
Access to shared data is serialized using mechanisms that ensure only one thread reads or writes the shared data at any time. Great care is required if a piece of code accesses multiple shared pieces of data—problems include race conditions, deadlocks, livelocks and starvation.
Thread-local storage 
Variables are localized so that each thread has its own private copy. These variables retain their values across subroutine and other code boundaries, and are thread-safe since they are local to each thread, even though the code which accesses them might be executed simultaneously by another thread.
Atomic operations 
Shared data are accessed by using atomic operations which cannot be interrupted by other threads. This usually requires using special machine language instructions, which might be available in a runtime library. Since the operations are atomic, the shared data are always kept in a valid state, no matter what other threads access it. Atomic operations form the basis of many thread locking mechanisms.

Examples

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.

Concurrent programming

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.

Difficulties

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.

See also

External links

References

  1. ^
    • Kerrisk, Michael. The Linux Programing Interface. No Starch Press, 2010, p. 655
    • Oracle. Multithreaded Programming Guide. Online , November 2010. -- "A procedure is thread safe when the procedure is logically correct when executed simultaneously by several threads."