Computer systems use various data structures to store and manipulate data during program execution. One type of such a data structure, shown in
Concurrent FIFO queues are one type of often-used concurrent data structures. A concurrent FIFO queue is a data structure sharable by concurrently executing threads that supports the usual enqueue and dequeue operations with linearizable FIFO semantics. Linearizability guarantees that queue operations appear atomic and can be combined with other operations in a modular way. In other words, linearizability provides the illusion that each operation on a concurrent FIFO queue applied by concurrent threads takes effect instantaneously at some point between invocation and response. The threads appear to be interleaved at the granularity of complete operations, and the order of non-overlapping operations is preserved.
Generally, implementations of concurrent FIFO queues are of two types: blocking (i.e., lock-based) and non-blocking (i.e., lock-free). In general, lock-based FIFO queue implementations offer limited robustness as processes are forced to wait to access a FIFO queue until a current process completes its access to the FIFO queue.
An implementation of a data structure is non-blocking (i.e., lock-free) if the implementation guarantees that at least one thread of those trying to update the data structure concurrently will succeed in completing its operation on the data structure within a bounded amount of time, assuming that at least one thread is active, regardless of the state of other threads. Non-blocking implementations generally rely on hardware support for an atomic primitive (i.e., a synchronization operation) such as a compare-and-swap instruction or the instruction pair load-linked and store-conditional.
A compare-and-swap instruction may operate on a single memory word or two memory words in single atomic operation. A single-word compare-and-swap operation (CAS) typically accepts three values, or quantities: a memory address A, a comparison value C, and a new value N. The CAS operation fetches and examines the contents V of memory at address A. If the contents V is equal to the comparison value C, then the new value N is stored into the memory location at address A, replacing the contents V. A Boolean return value indicates whether the replacement occurred. Whether V matches C, V is returned or saved in a register for later inspection (possibly replacing either C or N, depending on the implementation). Such an operation may be notated as “CAS(A, C, N).”
A double-word compare-and-swap operation (DCAS) typically accepts six values: two memory addresses A1 and A2, two comparison values C1 and C2, and two new values N1 and N2. The DCAS operation fetches and examines the contents V1 of memory at address A1 and V2 of memory at address A2. If the contents V1 are equal to the comparison value C1 and the contents V2 are equal to the comparison value C2, then the new value N1 is stored into the memory-location at address A1, replacing V1, and the new value N2 is stored into the memory location at address A2, replacing V2. A Boolean return value indicates whether the replacement occurred. Whether V1 and V2 matches C1 and C2, respectively, V1 and V2 are returned or saved in a register for later inspection. Such an operation may be notated as “DCAS(A1, A2, C1, C2, N1, N2).”
Load-linked and store-conditional operations must be used together to read, modify, and write a shared location. A load-linked operation returns the value stored at the shared location. A store-conditional operation checks if any other processor has since written to that shared location. If not, the location is updated and the operation returns success; otherwise, it returns failure.
Concurrent data structure implementations in non-garbage collected programming languages (e.g., the C programming language) that use CAS operations are susceptible to what is known as the ABA problem. If a thread reads a value A from a shared location, computes a new value, and then attempts a CAS operation, the CAS operation may succeed when it should not, if between the read and the CAS operation, other threads change the value of the shared location from A to B and back to A again (i.e., an ABA event). A typical solution to the ABA problem is to include a tag with the target memory location such that both are manipulated atomically and the tag is incremented with updates of the target location. The ABA problem does not occur in concurrent data structures implemented in garbage collected languages (e.g., the Java™ programming language).
One approach to implementing a concurrent FIFO queue is based on the algorithm of Michael and Scott (hereinafter the “MS-queue”). See Michael, M. M., and Scott, M. L., “Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms,” Proceedings of the 15th Annual ACM Symposium on Principles of Distributed Computing (1996) pp. 267-275. A key feature of this algorithm is that it permits uninterrupted parallel access to the head and tail of the FIFO queue.
According to one aspect of one or more embodiments of the present invention, a method of performing computer system operations comprises: implementing a data structure in memory, the data structure being accessible by a plurality of concurrent threads; and executing lock-free operations on the data structure, where executing lock-free operations comprises (i) adding a new value to the data structure, and (ii) removing an existing value from the data structure, where the removing comprises ensuring that an association between the existing value and another value in the data structure is consistent, and wherein the ensuring comprises correcting the association by traversing a plurality of next pointers in a plurality of nodes in the doubly-linked list to correct inconsistent previous pointers in the plurality of nodes.
According to another aspect of one or more embodiments of the present invention, a method of performing computer operations comprises: implementing a dynamically-sized data structure in memory, the data structure being disjointly accessible by a plurality of concurrent threads, where a request to retrieve a value from the data structure results in removal of an oldest value stored in the data structure; and performing a lock-free operation to add a new value to the data structure, where the lock-free operation to add comprises using only a single synchronization operation.
According to another aspect of one or more embodiments of the present invention, a method for performing computer system operations comprises: implementing a concurrent first-in-first-out (FIFO) queue as a dynamically-sized doubly-linked list; and enqueuing a node in the concurrent FIFO queue, where a single single-word synchronization operation is used to change a tail pointer, wherein the single single-word synchronization operation is the only synchronization operation used during the enqueuing.
According to another aspect of one or more embodiments of the present invention, a concurrent first-in-first-out (FIFO) queue in a memory of a computer system comprises: a doubly-linked list comprising a plurality of nodes, wherein each node of the plurality of nodes comprises a value, a next pointer, and a previous pointer; a head pointer, where the head pointer points to an oldest node of the plurality of nodes; and a tail pointer, where the tail pointer points to a youngest node of the plurality of nodes, where (i) when a node is added to the doubly-linked list, a single-word synchronization operation is used to change the tail pointer, where the single-word synchronization operation is the only synchronization operation used in adding the node, and (ii) when the node is removed from the doubly-linked list, a fix-up process is executed if a previous pointer of the node is inconsistent.
According to another aspect of one or more embodiments of the present invention, a computer system comprises: a dynamically-sized data structure implemented in a memory of the computer system, the data structure being disjointly accessible by a plurality of concurrent threads; and instructions in the memory to execute lock-free operations on the data structure, where the instructions to execute lock-free operations comprise instructions to correct an inconsistent association between a value in the data structure and another value in the data structure.
According to another aspect of one or more embodiments of the present invention, a method of facilitating concurrent programming using a first-in-first-out (FIFO) queue comprises: defining the FIFO queue as a doubly-linked list of dynamically-allocated nodes; defining an enqueue operation, where the enqueue operation comprises using only one synchronization operation; and defining a dequeue operation, where the dequeue operation comprises correcting inconsistent pointers between the nodes, where an execution of any of the operations is linearizable and non-blocking with respect to any other execution of the operations.
According to another aspect of one or more embodiments of the present invention, a method of facilitating concurrent programming using a data structure comprises: defining the data structure to be dynamically sized in memory; and defining operations on the data structure, wherein the operations comprise correcting an inconsistent association between a value in the data structure and another value in the data structure, where an execution of any one of the operations is linearizable and non-blocking with respect to any other execution of the operations.
Other aspects and advantages of the invention will be apparent from the following description and the appended claims.
Exemplary embodiments of the invention will be described with reference to the accompanying drawings. Like items in the drawings are shown with the same reference numbers.
Embodiments of the present invention relate to techniques for implementing and using a concurrent FIFO queue represented as an “optimistic” doubly-linked list (hereinafter “an optimistic FIFO queue”). Nodes of this optimistic doubly-linked list are allocated dynamically and links between the nodes are updated optimistically (i.e., assuming that threads concurrently accessing the FIFO queue will not interfere with each other) using a simple store operation rather than a synchronization operation (e.g., a CAS operation). Concurrent linearizable, non-blocking enqueue and dequeue operations on the two ends of the doubly-linked list proceed independently (i.e., are disjoint). These operations require only one successful single-word synchronization operation (e.g., a CAS operation) on the tail pointer and the head pointer of the doubly-linked list. If a bad ordering of operations on the optimistic FIFO queue by concurrently executing threads creates inconsistencies in the links between the nodes of the doubly-linked list, a fix-up process is applied to correct the inconsistencies.
The description that follows presents methods, pseudo code sequences, and data structures associated with an optimistic FIFO queue in accordance with one or more embodiments of the present invention. However, the methods, pseudo code sequences, and data structures presented will be understood by persons of ordinary skill in the art to be equally applicable to other concurrent shared data structures.
Linearizable, non-blocking enqueue and dequeue operations are provided to add a node (e.g., 304) to one end of the doubly-linked list (i.e., add a node to the tail of the queue) and to remove a node (e.g., 302) from the opposite end of the doubly-linked list (i.e., remove a node from the head of the queue), respectively, to implement the requisite FIFO semantics of the optimistic FIFO queue (300). In an embodiment of the present invention, as shown in
Referring back to
By setting the next pointer (e.g., 340) in the new node (e.g., 304) before modifying the tail pointer (308), an enqueue process in accordance with one or more embodiments of the present inventions ensures that the next pointers of all nodes in the doubly-linked list are consistent (ignoring the possibility of ABA problems). A next pointer (e.g., 326) in a node (e.g., 320) is consistent if it points to a node (e.g., 318) added to the doubly-linked list immediately prior to that node (e.g., 320). If the next pointer (e.g., 340) in the new node (e.g., 304) could have been changed after the tail pointer (308) was modified, the next pointer (e.g., 340) could be inconsistent if a concurrent enqueue operation completes in the time between the tail pointer (308) modification and the changing of the next pointer (e.g., 340).
The enqueue process sets previous pointers (e.g., 342) optimistically. That is, no measures are taken to ensure that an enqueue process actually sets the previous pointer after the successful CAS operation on the tail pointer (308). Therefore, previous pointers (e.g., 314, 348, 328, 342, 350) in nodes (e.g., 302, 318, 320, 316, 304) of the doubly-linked list may be inconsistent. A previous pointer (e.g., 328) of a node (e.g., 320) is inconsistent if it does not point to a node (e.g., 316) added to the doubly-linked list immediately after that node (e.g., 320) was added. As discussed below, the dequeue operation relies on the previous pointer of the node being dequeued to determine which node is to become the new head of the doubly-linked list. Therefore, the dequeue operation includes functionality to handle the possibility that this previous pointer is inconsistent.
The dequeue operation includes functionality to repetitively execute a dequeue process until the node (e.g., 302) at the head of the queue is removed or to exit if the queue is empty. In this dequeue process, the flow if the doubly linked list includes at least two nodes that are not the dummy node is as follows. First, a determination is made as to whether the previous pointer (e.g., 314) of the node (e.g., 302) currently pointed to by the head pointer (306) is consistent. If the previous pointer (e.g., 314) is not consistent, a fix-up process is executed to repair inconsistent previous pointers in the doubly-linked list. This fix-up process walks through the doubly-linked list from the tail node (e.g., 304) to the head node (e.g., 302) using the chain of next pointers (e.g., 340, 344, 326, 346, 312) in the nodes (e.g., 304, 316, 320, 318, 302), correcting any inconsistent previous pointers. After the fix-up process completes, the dequeue process is retried.
If the previous pointer (e.g., 314) is consistent, a single-word synchronization operation (e.g., CAS Head 324) is used to attempt to atomically modify the head pointer (306) to point to the previous node (e.g., 318) in the doubly-linked list. The previous pointer (e.g., 314) in the node (e.g., 302) currently pointed to by the head pointer (306) designates which node (e.g., 318) is previous in the doubly-linked list. If the single-word synchronization operation (e.g., CAS Head 324) succeeds, the dequeue process ends. If the single-word synchronization operation (e.g., CAS Head 324) does not succeed, the dequeue process is retried. Thus, in one or more embodiments of the present invention, during the flow of the dequeue process, the single-word synchronization operation (e.g., CAS Head 324) on the head pointer (306) is the only synchronization operation used.
In embodiments of the present invention implemented in a non-garbage collected language, a tagging mechanism may be used to avoid possible ABA event problems and to provide a mechanism for detecting inconsistent previous pointers. A tag (not shown) is added to each pointer (i.e., the head pointer (306), the tail pointer (308), the next pointers (e.g., 340, 344, 326, 346, 312), and the previous pointers (e.g., 314, 348, 328, 342, 350)) in the optimistic FIFO queue (300) and functionality is added to the enqueue and dequeue operations to use these tags to avoid ABA problems. In addition, the dequeue operation uses the tags to detect the presence of inconsistent previous pointers. The tags in the head pointer (306) and the tail pointer (308) are atomically modified in the single-word synchronization operations that modify the head pointer (306) and the tail pointer (308) during the dequeue and enqueue operations, respectively. An embodiment of such a tagging mechanism is presented in the pseudo code of
In embodiments of the present invention implemented in a garbage collected language, the next and previous pointers may be initialized to a null value when a new node is created. If the next successful enqueue operation that changed the tail pointer (308) does not also modify this previous pointer (e.g., 350) because it was delayed or halted, the previous pointer remains null. During a subsequent dequeue operation, if the previous pointer (e.g., 314) of the node (e.g., 302) pointed to by the head pointer (306) contains a null value, a fix-up process is executed.
In one or more embodiments of the present invention, the optimistic FIFO queue (300) may enter certain atypical states that are addressed as a part of the dequeue process. In an embodiment of the present invention, as shown in
In an embodiment of the present invention, as shown in
If the previous pointer is consistent (Step 502), an attempt is made to change the head pointer using a single-word synchronization operation (Step 506). The single-word synchronization operation tries to change the head pointer to point to the node pointed to by the previous pointer of the node to be dequeued. If the single-word synchronization operation is not successful (Step 508), the dequeue process restarts (Step 499). The cycle of fixing inconsistent previous pointers (Steps 499, 500, 502, 504) or attempting to change the head pointer if the previous pointer is consistent (Steps 499, 500, 502, 506, 508) may repeat until the single-word synchronization operation is successful. If the single-word synchronization operation is successful (Step 508), the dequeue process returns the value in the dequeued node (Step 509) and subsequently exits.
In one or more embodiments of the present invention, in garbage-collecting languages, a pre-allocated dummy node may not be used. Instead, a node will be defined as a dummy node if it contains a pre-defined dummy value. Thus, during initiation of a queue, a new node with the pre-defined dummy value will be created, and both the tail pointer and the head pointer will be set to point to the new node. When removing the last node in the queue, a new node with the pre-defined dummy value may be created, and the tail point and the head pointer will be set to point to the new node in the same manner as during initiation of the queue described above.
A concurrent thread requests the addition of a value to an optimistic FIFO queue (i.e., an enqueue operation) by invoking the enqueue function shown in
A concurrent thread requests the removal of the oldest value from an optimistic FIFO queue (i.e., an dequeue operation) by invoking the dequeue function shown in
The flow of this dequeue process, ignoring special states of the queue q (e.g., when the queue q is empty, when there is only one node in the queue q, or when the head pointer points to the dummy node and the tail pointer does not), is as follows: (i) determine whether the previous pointer of the node currently pointed to by the head pointer is consistent (i.e., the tag of the previous pointer is the same as the tag of the head pointer) (line 11); (ii) if the previous pointer is inconsistent, call function fixList, shown in
Referring back to
Embodiments of the present invention provide practical implementations of a concurrent lock-free FIFO queue that yield better performance than other known practical implementations. The enqueue and dequeue operations of the present invention each typically require successful completion of one single-word synchronization operation while the enqueue operation of the best known prior art concurrent lock-free FIFO queue implementation requires the successful completion of two single-word synchronization operations. In addition, contention for the FIFO queue is potentially reduced due to the reduced number of synchronization operations.
Embodiments of the present invention may be implemented on virtually any type of computer regardless of the platform being used. For example, as shown in
While the invention has been described with respect to a limited number of embodiments, those skilled in the art, having benefit of this disclosure, will appreciate that other embodiments can be devised which do not depart from the scope of the invention as disclosed herein. Accordingly, the scope of the invention should be limited only by the attached claims.
| Number | Name | Date | Kind |
|---|---|---|---|
| 6173373 | Bonola | Jan 2001 | B1 |
| 6668291 | Forin et al. | Dec 2003 | B1 |
| 20010047361 | Martin et al. | Nov 2001 | A1 |
| 20010056420 | Steele et al. | Dec 2001 | A1 |