ABA problem
Encyclopedia
In multithreaded
computing
, the ABA problem occurs during synchronization, when a location is read twice, has the same value for both reads, and "value is the same" is used to indicate "nothing has changed". However, another thread can execute between the two reads and change the value, do other work, then change the value back, thus fooling the first thread in to thinking "nothing has changed" even though the second thread did work that violates that assumption.
The ABA problem occurs when multiple threads
(or processes
) accessing shared memory
interleave. Below is the sequence of events that will result in the ABA problem:
Although can continue executing, it is possible that the behavior will not be correct due to the "hidden" modification in shared memory.
A common case of the ABA problem is encountered when implementing a lock-free data structure. If an item is removed from the list, deleted, and then a new item is allocated and added to the list, it is common for the allocated object to be at the same location as the deleted object due to optimization. A pointer to the new item is thus sometimes equal to a pointer to the old item which is an ABA problem.
In this scenario, the 'A' state is when there is a briefcase next to Charlie, and the 'B' state is when there is nothing next to him. Originally, his briefcase starts in 'A' state. If Charlie had turned around in between the time Albert took his real briefcase and replaced it with the fake, he would've seen his briefcase gone ('B' state) and sounded the alarm. Unfortunately, when he turned back he observed 'A' state and had no choice but to assume that 'B' state never happened.
See the workaround section to find out how Charlie could have prevented this misfortune.
Following are a couple of code examples:
Consider this lock-free stack
:
This code can normally prevent problems from concurrent access, but suffers from ABA problems. Consider the following sequence:
Stack initially contains top → A → B → C
Thread 1 starts running pop:
ret = A;
next = B;
Thread 1 gets interrupted just before the CompareAndSwap...
Now the stack is top → A → C
When Thread 1 resumes:
CompareAndSwap(A, A, B)
This instruction succeeds because it finds top ret (both are A), so it sets top to next (which is B). As B has been deleted the program will access freed memory when it tries to look the first element on the stack. Accessing freed memory is undefined, in the sense there are numerous difficult to debug ways your program will crash, so a program will soon crash in a difficult to debug manner.
Workarounds
Going back to the previous example of Charlie and his briefcase, what could Charlie have done differently?
There are a number of ways that Charlie could have prevented this from happening, even though he can't open the briefcase. For one, he could've chained the real briefcase to the seat. That way, Albert would have to cut the chain to steal the briefcase, and Charlie would notice the cut chain and sound the alarm. This is what the LL/SC instruction on some processors attempts to do. Another solution would be for Charlie to write down the serial number of his real briefcase, and check it after every time he looks away from it. This is how Double-Word Compare-And-Swap
Tagging works. Automatic Garbage Collection
, along with other techniques like Hazard Pointers
, deal with this problem by ensuring that there is no other briefcase in the world that looks like Charlie's briefcase. When Charlie, his boss, and whoever else cares about the briefcase don't need it anymore, it is destroyed. Then, and only then, is another briefcase that looks like his allowed to be created.
Below are examples of code mechanisms that implement the ideas above.
A common workaround is to add extra "tag" bits to the quantity being considered. For example, an algorithm using compare and swap on a pointer might use the low bits of the address to indicate how many times the pointer has been successfully modified. Because of this, the next compare-and-swap will fail, even if the addresses are the same, because the tag bits will not match. This does not completely solve the problem, as the tag bits will eventually wrap around, but helps to avoid it. Some architectures provide a double-word compare and swap, which allows for a larger tag. This is sometimes called ABA' since the second A is made slightly different from the first.
A correct but expensive approach is to use intermediate nodes that are not data elements and thus assure invariants as elements are inserted and removed [Valois].
Another approach is to defer reclamation of removed data elements. One way to defer reclamation is to run the algorithm in an environment featuring an automatic garbage collector
. Another way to defer reclamation is to use one or more hazard pointer
s, which are pointers to locations that otherwise cannot appear in the list. Each hazard pointer represents an intermediate state of an in-progress change; the presence of the pointer assures further synchronization [Doug Lea]. Yet another way to defer reclamation is to use read-copy update (RCU)
, which involves enclosing the update in an RCU read-side critical section and then waiting for an RCU grace period before freeing any removed data elements. Using RCU in this way guarantees that any data element removed cannot reappear until all currently executing operations have completed.
Some architectures provide "larger" atomic operations such that, as example, both forward and backward links in a doubly linked list can be updated atomically.
Some architectures provide a load linked, store conditional
instruction in which the store is performed only when there are no other stores of the indicated location. This effectively separates the notion of "storage contains value" from "storage has been changed". Examples include DEC Alpha, MIPS, PowerPC and ARM (v6 and later). However no practical implementations of load linked will directly solve the ABA problem. [Michael]
Thread (computer science)
In computer science, a thread of execution is the smallest unit of processing that can be scheduled by an operating system. The implementation of threads and processes differs from one operating system to another, but in most cases, a thread is contained inside a process...
computing
Computer science
Computer science or computing science is the study of the theoretical foundations of information and computation and of practical techniques for their implementation and application in computer systems...
, the ABA problem occurs during synchronization, when a location is read twice, has the same value for both reads, and "value is the same" is used to indicate "nothing has changed". However, another thread can execute between the two reads and change the value, do other work, then change the value back, thus fooling the first thread in to thinking "nothing has changed" even though the second thread did work that violates that assumption.
The ABA problem occurs when multiple threads
Thread (computer science)
In computer science, a thread of execution is the smallest unit of processing that can be scheduled by an operating system. The implementation of threads and processes differs from one operating system to another, but in most cases, a thread is contained inside a process...
(or processes
Process (computing)
In computing, a process is an instance of a computer program that is being executed. It contains the program code and its current activity. Depending on the operating system , a process may be made up of multiple threads of execution that execute instructions concurrently.A computer program is a...
) accessing shared memory
Shared memory
In computing, shared memory is memory that may be simultaneously accessed by multiple programs with an intent to provide communication among them or avoid redundant copies. Depending on context, programs may run on a single processor or on multiple separate processors...
interleave. Below is the sequence of events that will result in the ABA problem:
- Process reads value A from shared memory,
- is preemptedPreemption (computing)In computing, preemption is the act of temporarily interrupting a task being carried out by a computer system, without requiring its cooperation, and with the intention of resuming the task at a later time. Such a change is known as a context switch...
, allowing process to run, - modifies the shared memory value A to value B and back to A before preemption,
- begins execution again, sees that the shared memory value has not changed and continues.
Although can continue executing, it is possible that the behavior will not be correct due to the "hidden" modification in shared memory.
A common case of the ABA problem is encountered when implementing a lock-free data structure. If an item is removed from the list, deleted, and then a new item is allocated and added to the list, it is common for the allocated object to be at the same location as the deleted object due to optimization. A pointer to the new item is thus sometimes equal to a pointer to the old item which is an ABA problem.
Example
The easiest-to-understand example of the ABA problem would be the following real-life scenario:- Charlie is at an airport carrying a briefcase with a large sum of money in it. He is carrying it for his boss, so he does not know the suitcase's combination. An attractive and provocatively-dressed woman (Sharon) approaches him and strikes up a conversation. While Sharon is distracting Charlie, her partner-in-crime (Albert) decides to seize the opportunity to grab the money. He realizes that if Charlie turns around and sees that his briefcase has disappeared, he will sound an alarm; after that, it is not likely that he and Sharon will make it out of the airport without being stopped by security.
- Instead, Albert quickly takes Charlie's briefcase and replaces it with an identical briefcase filled with sand to match the weight of the money. Sharon finishes up her conversation with Charlie, he gets up, gets on the plane, and leaves. He is completely unaware that the briefcase now contains only sand. When Charlie's boss finally opens the briefcase, it is likely Charlie will be in a lot of trouble.
In this scenario, the 'A' state is when there is a briefcase next to Charlie, and the 'B' state is when there is nothing next to him. Originally, his briefcase starts in 'A' state. If Charlie had turned around in between the time Albert took his real briefcase and replaced it with the fake, he would've seen his briefcase gone ('B' state) and sounded the alarm. Unfortunately, when he turned back he observed 'A' state and had no choice but to assume that 'B' state never happened.
See the workaround section to find out how Charlie could have prevented this misfortune.
Following are a couple of code examples:
Consider this lock-free stack
Stack (data structure)
In computer science, a stack is a last in, first out abstract data type and linear data structure. A stack can have any abstract data type as an element, but is characterized by only three fundamental operations: push, pop and stack top. The push operation adds a new item to the top of the stack,...
:
This code can normally prevent problems from concurrent access, but suffers from ABA problems. Consider the following sequence:
Stack initially contains top → A → B → C
Thread 1 starts running pop:
ret = A;
next = B;
Thread 1 gets interrupted just before the CompareAndSwap...
Now the stack is top → A → C
When Thread 1 resumes:
CompareAndSwap(A, A, B)
This instruction succeeds because it finds top ret (both are A), so it sets top to next (which is B). As B has been deleted the program will access freed memory when it tries to look the first element on the stack. Accessing freed memory is undefined, in the sense there are numerous difficult to debug ways your program will crash, so a program will soon crash in a difficult to debug manner.
Workarounds
Going back to the previous example of Charlie and his briefcase, what could Charlie have done differently?
There are a number of ways that Charlie could have prevented this from happening, even though he can't open the briefcase. For one, he could've chained the real briefcase to the seat. That way, Albert would have to cut the chain to steal the briefcase, and Charlie would notice the cut chain and sound the alarm. This is what the LL/SC instruction on some processors attempts to do. Another solution would be for Charlie to write down the serial number of his real briefcase, and check it after every time he looks away from it. This is how Double-Word Compare-And-Swap
Compare-and-swap
In computer science, the compare-and-swap CPU instruction is a special instruction that atomically compares the contents of a memory location to a given value and, only if they are the same, modifies the contents of that memory location to a given new value...
Tagging works. Automatic Garbage Collection
Garbage collection (computer science)
In computer science, garbage collection is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no longer in use by the program...
, along with other techniques like Hazard Pointers
Hazard pointer
In a multithreaded computing environment, a hazard pointer is an element used by a method that allows the memory allocated to the nodes of lock-free dynamic shared objects to be reclaimed....
, deal with this problem by ensuring that there is no other briefcase in the world that looks like Charlie's briefcase. When Charlie, his boss, and whoever else cares about the briefcase don't need it anymore, it is destroyed. Then, and only then, is another briefcase that looks like his allowed to be created.
Below are examples of code mechanisms that implement the ideas above.
A common workaround is to add extra "tag" bits to the quantity being considered. For example, an algorithm using compare and swap on a pointer might use the low bits of the address to indicate how many times the pointer has been successfully modified. Because of this, the next compare-and-swap will fail, even if the addresses are the same, because the tag bits will not match. This does not completely solve the problem, as the tag bits will eventually wrap around, but helps to avoid it. Some architectures provide a double-word compare and swap, which allows for a larger tag. This is sometimes called ABA' since the second A is made slightly different from the first.
A correct but expensive approach is to use intermediate nodes that are not data elements and thus assure invariants as elements are inserted and removed [Valois].
Another approach is to defer reclamation of removed data elements. One way to defer reclamation is to run the algorithm in an environment featuring an automatic garbage collector
Garbage collection (computer science)
In computer science, garbage collection is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no longer in use by the program...
. Another way to defer reclamation is to use one or more hazard pointer
Hazard pointer
In a multithreaded computing environment, a hazard pointer is an element used by a method that allows the memory allocated to the nodes of lock-free dynamic shared objects to be reclaimed....
s, which are pointers to locations that otherwise cannot appear in the list. Each hazard pointer represents an intermediate state of an in-progress change; the presence of the pointer assures further synchronization [Doug Lea]. Yet another way to defer reclamation is to use read-copy update (RCU)
Read-copy-update
In computer operating systems, read-copy-update is a synchronization mechanism implementing a kind of mutual exclusionPlease note that RCU does not implement mutual exclusion in the conventional sense: RCU readers can and do run concurrently with RCU updates...
, which involves enclosing the update in an RCU read-side critical section and then waiting for an RCU grace period before freeing any removed data elements. Using RCU in this way guarantees that any data element removed cannot reappear until all currently executing operations have completed.
Some architectures provide "larger" atomic operations such that, as example, both forward and backward links in a doubly linked list can be updated atomically.
Some architectures provide a load linked, store conditional
Load-Link/Store-Conditional
In computer science, load-link and store-conditional are a pair of instructions that together implement a lock-free atomic read-modify-write operation....
instruction in which the store is performed only when there are no other stores of the indicated location. This effectively separates the notion of "storage contains value" from "storage has been changed". Examples include DEC Alpha, MIPS, PowerPC and ARM (v6 and later). However no practical implementations of load linked will directly solve the ABA problem. [Michael]