I Introduction
As predicted [1], concurrent data structures have arrived at a tipping point where change is inevitable. These drivers converge to motivate new thinking about correctness:

Architectural demands to utilize multicore and distributed resources [2]
There are a number of correctness conditions for concurrent systems presented in literature [32, 33, 34, 35, 36, 12, 37]. A discussion of these concurrent correctness conditions found in Section VA. Here follows a brief synopsis.
A concurrent history defines the events in a system of concurrent processes and objects. An object subhistory, , of a history is the subsequence of all events in that are invoked on object [34]. A sequential specification for an object is a set of sequential histories for the object [35]. A sequential history is legal if each subhistory in for object belongs to the sequential specification for .
The difference between the correctness conditions resides in the allowable method call orderings. Serializability [32] places no constraints on the method call order. Sequential Consistency [33] requires that each method call take effect in program order. Linearizability [34] requires that each method call take effect at some instant between its invocation and response. A correctness condition is compositional if and only if, whenever each object in the system satisfies , the system as a whole satisfies [35]. Linearizability [34] is a desirable correctness condition for systems of many shared objects where the ability to reason about correctness in a compositional manner is essential. Sequential consistency [33] is suitable for an independent system such as a hardware memory interface that requires program order of method calls to be preserved. Other correctness conditions [36, 12, 37] are defined that permit relaxed behaviors of method calls to obtain performance enhancements in concurrent programs.
These correctness conditions require a concurrent history to be equivalent to a sequential history. While this way of defining correctness enables concurrent programs to be reasoned about using verification techniques for sequential programs [38, 39], it imposes several inevitable limitations on a concurrent system. Such limitations include 1) requiring the specification of the behavior of a concurrent system to be described as if it were a sequential system, 2) restricting the method calls in a concurrent history to respect data structure semantics and to be ordered in a way that satisfies the correctness condition, leading to performance bottlenecks, and 3) burdening correctness verification with a worstcase time complexity of to compute the sequential histories for the possible interleavings of concurrent method calls. Some correctness verification tools have provided optimizations [40, 37] integrated into model checkers that accept user annotated linearization points to reduce the search space of possible sequential histories to time in addition to the time to perform the model checking with dynamic partialorder reductions, where is the number of processes, is the maximum size of the search stack, and is the number of transitions explored in the search space [41]. However, this optimization technique for correctness verification is only effective if all methods have fixed linearization points.
This paper proposes Quantifiability, a new definition of concurrent correctness that does not require reference to a sequential history. Freeing analysis from this historical artifact of the era of single threaded computation and establishing a purely concurrent correctness condition is the goal of this paper. Quantifiability eliminates the necessity of demonstrating equivalence to sequential histories by evaluating correctness of a concurrent history based solely on the outcome of the method calls. Like other conditions it does require atomicity of method calls and enables modular analysis with its compositional properties.
Quantifiability supports separation of concerns. Principles of correctness are not mixed with realtime ordering or data structure semantics. These are modifiers or constraints on the system. The disregard of realtime order and conservation of method calls enables concurrent histories to be represented in vector space. Although it is not possible to foresee all uses of the vector space model, this paper will demonstrate the use of linear algebra to efficiently verify concurrent histories as quantifiable.
The following presentation of quantifiability and its proposed verification technique draws unabashedly on the work of Herlihy, who shaped the way a generation thinks about concurrency. This paper repositions concurrent correctness in a way that overcomes the inherent limitations associated with defining correctness through equivalence to sequential histories. Contributions to the field are:

We propose quantifiability as a concurrent correctness condition and illustrate its benefits over other correctness conditions.

We show that quantifiability is compositional and nonblocking.

We introduce linear algebra as a formal tool for reasoning about concurrent systems.

We present a verification algorithm for quantifiability with a significant improvement in time complexity compared to analyzing many sequential histories using traditional correctness conditions.

A quantifiably correct concurrent stack and queue are implemented and shown to scale well.
Ia First Principles
Among principles that describe concurrent system behavior, first principles define what things happen while secondary principles such as timing and order are modifiers on them. The conditions defined in secondary principles do not make sense without the first, but the reverse is not the case [42]. This view accords with intuition: Tardiness to a meeting is secondary to there being a meeting at all. In a horse race, first principles define that the jockeys and horses are the same ones who started; only then does the order of finish makes sense. The intuition that the events themselves are more important than their order, and that conservation is a prerequisite for ordering will be motivated with the following examples.
Consider history on object with LastInFirstOut (LIFO) semantics. The notation follows the convention (), where is the method, is the object, is the process, and is the item to be input or output. is serializable and also sequentially consistent. is not linearizable, but it would be if the interval of were slightly extended to overlap or a similar adjustment were made relative to . Linearizability requires determining “happens before” relationships for among all method calls to project them onto a sequential timeline. Doing this with a shared clock timing the start and end of each method call is not feasible [43]. What is available, given some interprocess communication, is a logical clock [44]. Once established, linearizability is compositional [45]. Linearizability is sometimes “relaxed”, creating loopholes to enable performance gains. Without timing changes, is linearizable using LIFO semantics where [9].
Consider history on the same object . is not serializable, not sequentially consistent, not linearizable, and no changes in timing will allow to meet any of these conditions. Also, there is no practical relaxation of semantics that accepts . There is an essential difference in the correctness of and . What happened in history is intuitively acceptable, given some adjustments to when (timing) and how (relaxed semantics) it happened. What happened in history is impossible, as it creates the return value 3 from nothing. As in the equestrian example, item 3 is not one of the starting horses. The method calls on object are not conserved. A correctness condition that captures the difference between and would allow separating the concerns of what happened and when according to the (possibly relaxed) semantics.
History has two objects and . The projections and are serializable. The combined history is serializable. Projections and are also sequentially consistent. However, their composition into is not sequentially consistent. Sequential consistency is not compositional [44]. Projection is not linearizable, therefore is also not linearizable.
Conditional semantics are semantics that enable a method to return null upon reaching an undefined object state. History is the same as with the exception of introducing conditional semantics for , making explicit a common relaxation of how a stack works. With the conditional becomes sequentially consistent, yielding multiple correct orderings and end states.
But conditional is not consistent with early formal definitions of the stack abstract data type where on an empty stack threw an error [46] or a signal [47]. The semantics of these exceptions were taken seriously [48]. Invariants prevented exceptions, and there was “no guarantee” of the result if they were violated [49]. The conditional can be traced to the literature on performance [50], where the requirement to handle errors and check invariants is ignored. Conditional semantics remain prevalent in recent work, extending to proofs of correctness allowing two different linearization points with respect to the same method calls [20].
Allowing a stack pop to conditionally return null is not merely a convenience for performance benchmarks. Altering the semantics of to return null is as significant as changing the order in which items are returned. Imagine the task of debugging even a simple sequential process if the result of method calls defined inside the program were conditional upon events occurring outside the program. For example, define x as a method , which a process calls respecting the invariant that x>0. Now suppose that the method occasionally returns null and fails to decrement x. This is analogous to the behavior of a concurrent stack with a conditional .
Type theory supports the view that conditional semantics, although permissible, alter the method just as fundamentally as other semantic changes. A wellcited formal definition of an abstract data type confirms this [49], representing both the empty stack invariant and the ordering of return values for as subtypes of an unordered “bag” of items. Each is a separate concern, explicitly implemented within a type system.
History illustrates another problem with the conditional . Consider a stack that allocates a scarce resource. issued a request before and repeats it soon after, but gets nothing. might be repeated many times with and exchanging the item. The scheduler allocates twice as many requests per cycle to as either or , so why is there starvation? It is because conditional is inherently unfair. Although is not blocked in the sense of waiting to complete the method [51], conditional causes it to repeatedly lose its place in the ordering of requests. It might be called “progress without progress.” Recall that serializability causes inherent blocking and this was used to show the benefits of linearizability [34]. A new correctness condition should be free of inherent unfairness as well. In a quantifiably correct system the first request would remain active, not blocking unless the thread desires to wait, in the program can be made asynchronous by allocating an address as an inbox so it reads and checking as desired to see if the address is updated.
IB Temporal Order of Asynchronous Method Calls
Quantifiability places less importance on order than linearizability, and is more like serializability. Several authors promote the idea that linearizability and serializability are of different categories and should be applied to different domains. This recognizes the fact that there are important domains where the temporal order does not matter. Database systems are mentioned as an example, where the semantics of related atomic method calls in a transaction determine correctness, not the temporal order of (mostly) unrelated individual method calls.
The point of relaxed semantics of concurrent system ordering is to allow method calls to be executed out of order to effectively utilize multiple processors. In general relaxed semantics apply to all method calls, even those in the same process. A push followed by another push in the same process on a stack with relaxation of 4 may be reordered so that the first push ends up second, and the second push goes into fourth place. A concurrent system with shared objects does not need to be strictly sequentially consistent, because calls to the shared objects are asynchronous and the result returned depends upon what all the other processes did in the interval between calls in the program order.
An appropriate baseline for the use of relaxed semantics can be stated for a given concurrent system. Consider a system with P processes. At any moment there may be P candidates as the next item put into or removed from the container. The only way to know which item it will be is if you are controlling which process is given the CAS. That process will win. The others will spin with contention. There are up to P possible candidates for the next item. A 64 processor system should use at least 64 as its relaxation level because the placement of the next item depends upon which of 64 processors wins the CAS race. It is not something over which the data structure itself has control, a fact that is ignored in most system models. Therefore it follows that any system with P processes should adopt a relaxation level of
at least P.Quantifiability recognizes that order can be critical. However the temporal order of unrelated method calls rarely is, and should not be used to determine program correctness. Instead of starting from the most restrictive ordering semantics and relaxing them, quantifiability starts by assigning a value to the method calls and then applying constraints on temporal, semantic, or transactional ordering that are appropriate to the application.
Multicore programming is considered an art [35] and is generally regarded as difficult. The projection of a concurrent history onto a sequential timeline provided an abstraction to understand systems with a few processes and led to definitions of their correctness. Linearizability, being compositional, ensured that reasoning about system correctness is linear with respect to the number of objects. But verifying linearizability for an individual object is not linear in the number of method calls. Systems today may have thousands of processes and millions of method calls, far beyond the capacity of current verification tools. The move from art to an engineering discipline requires a new correctness condition with the following desirable properties:

Conservation What happens is what the methods did. Return values cannot be pulled from thin air (history ). Method calls cannot disappear into thin air (history ).

Measurable Method calls have a certain and measurable impact on system state, not sometimes null (history ).

Compositional Demonstrably correct objects and their methods may be combined into demonstrably correct systems (history ).

Unconstrained by Timing Correctness based on timing has high complexity and can be handled in the type system rather than in the correctness condition (history and ).

Lockfree, Waitfree, StarvationFree Design of the correctness condition should not limit or prevent system progress.
Ii Definition
Quantifiability is concerned with the impact of method calls on the system as opposed to the projection of a method call onto a sequential timeline. The configuration of an arbitrary element comprises the values stored in that element. The system state is the configuration of all the objects that represents the outcome of the method calls by the processes.
Iia Principles of Quantifiability
A familiar way to introduce a correctness condition is to state principles that must be followed for it to be true [35]. Quantifiability embodies two principles.
Principle 1.
Method conservation: Method calls are first class objects in the system that must succeed, remain pending, or be explicitly cancelled.
Principle 1 requires that every instance of a process calling a method, including any arguments and return values specified, is part of the system state. Method calls are not ephemeral requests, but “first class” [52, 53] members of the system. All remain pending until they succeed or are explicitly cancelled. Method calls may not be cancelled implicitly as in the conditional . Actions expected from the method by the calling process must be completed. This includes returning values (if any) and making the expected change to the state of the concurrent object on which the method is defined.
Principle 2.
Method quantifiability: Method calls have a measurable impact on the system state.
Principle 2 requires that every method call owns a scalar value, or metric, that reflects its impact on system state. There is some total function that computes this metric for each instance of a method call.
Building on Principle 1 that conserves the method calls themselves, Principle 2 requires that a value can be assigned to the method call. All method calls “count.” The conservation of method calls along with the measurement of their impact on system state is what gives quantifiability its name. Values are assigned as part of the correctness analysis. As with concepts such as linearization points, these values are not necessarily part of the data structure, but are artifacts for proving correctness.
The assignment of values to the method calls may be straightforward. For the stack abstract data type, is set to +1 and is set to 1. Sometimes value assignments are subtle: Principle 1 requires that pending reads are first class members of the system state, so completing them is a state change. Reads will have a small but measurable impact, unlike reads in other system models that are considered to have no effect on system state. Probes such as a method on a set data type would also have a value.
It may appear as though quantifiability is equivalent to serializability. However, quantifiability does not permit method calls to return null upon reaching an undefined object state while serializability does permit this behavior. Quantifiability measures the outcome of every method call by virtue of its completion. This subtle difference directly impacts the complexity of analysis, and can lead to throughput increases for quantifiability when designing a data structure. Quantifiable implementations learn from relaxed semantics to “save” a method call in an accumulator rather than discarding it due to a data structure configuration where the method call could not be immediately fulfilled. Quantifiable data structure design is discussed in further details in Section VI.
IiB System Model
A concurrent system is defined here as a finite set of methods, processes, objects and items. Methods define what happens in the system. Methods are defined on a class of objects but affect only the instances on which they are called. Processes are the actors who call the methods, either in a predetermined sequence or asynchronously driven by events. Objects are encapsulated containers of concurrent system state. Objects are where things happen. Items are data passed as arguments to and returned as a result from completed method calls on the concurrent objects. Items are passive and may not have methods defined on them. Method invariants and semantics place constraints such as order, defining how things happen. Quantifiable concurrent histories are serializable so every method call takes effect during the interval spanning the history, meaning that when method calls occur may be reordered to achieve correctness. A summary of the components of the system model are presented in Table I.
Component  Role 

Processes  Processes call methods 
Methods  Methods act on instances of objects, only on whose abstract data type that method is defined 
Objects  Objects are instances of an abstract data type 
Items  Items are data passed as input to methods or returned as output from methods 
A method call is a pair consisting of an invocation and next matching response [34]. Each method call is specified by a tuple (Method, Process, Object, Item). Method calls with input or output that comprises multiple items can be represented as a single structured item. An execution of a concurrent system is modeled by a concurrent history (or simply history), which is a multiset of method calls [34]. An invocation is pending in history if no matching response follows the invocation [34]. Although the domain of possible methods, processes, objects, and items is infinite, actual concurrent histories are a small subset of these. Method calls in the history are done by a process on an object and further specified with items passed or returned.
It is not unusual when discussing concurrent histories to speak of, “the projection of a history onto objects.” However the focus from there has always been on building sequential histories, so the literature does not extend this language to bring the analysis of concurrent histories formally into the realm of linear algebra. Quantifiability facilitates this extension, with fruitful consequences.
IiC Vector Space
A vector is an ordered tuple of numbers, where is an arbitrary positive integer. A row vector is a vector with a rowbycolumn dimension of 1 by . A column vector is a vector with a rowbycolumn dimension of by 1. In this section our system model is mapped to a vector space over the field of real numbers . The system model is isomorphic to vector spaces over described in matrix linear algebra textbooks [54]. From this foundation, analysis of concurrent histories can proceed using the tools of linear algebra.
The components of the system model are represented as dimensions in the vector space, written in the order Methods, Processes, Objects and Items. The basis vector of the history is the Cartesian product of the dimensions . Each unique configuration of the four components defines a basis for a vector space over the real numbers. The spaces thus defined are of finite dimension. In this model, orthogonal means that dimensions are independent. For example, the projection of history onto object () is separate from the projection of history onto object (). An orthogonal basis is a basis whose vectors are orthogonal. It is necessary to define an orthogonal basis because each noninteracting method call with distinct objects, processes, and items is an independent occurrence from every other combination.
A history is represented by a vector with elements corresponding to the basis vector uniquely defined by the concurrent system. Principle 2 states that each method call has a value. These are the values represented in the elements of the history vector. On a LIFO stack, and pop methods are inverses of each other. An important difference is that is completed without dependencies in an unbounded stack, whereas returns the next available item, which may not arrive for some time. A history that shows a completed must account for the source of the item being returned, either in the initial state or in the history itself. The discussion of history in Section IA showed this is common to the analysis of serializability, quantifiability and linearizability.
Concurrent histories can be written as column vectors whose elements quantify the occurrences of each unique method call, that is, a vector of coordinates over acting on a basis constructed of the Methods, Processes, Objects and Items involved. History in Section IA can be written:
(1) 
The basis shown above is orthogonal due to the independence of the dimensions. The process and method dimensions are clearly independent from each other, one is the caller, the other is being called. Their roles do not permit overlap. Likewise the sets of objects and the items they manipulate should be distinct in order to apply this model. Every possible history using the given methods, processes, objects and items can be written in terms of the basis.
Two histories with the same basis can be added. Let H6 be the history , , , . can be added to history to get a new history:
(2) 
Scalar multiplication is simply repeating a history:
(3) 
The history vector has the potential to be large considering that the basis vector is defined according to the Cartesian product of the dimensions . However, the history vector will likely be sparse unless all possible combinations in which the items passed to the methods invoked by the processes on the objects occur in the history. If the history vector is sparse, then the algorithms analyzing the history vector can be optimized to take advantage of the sparse structure. With a dense history vector where the majority of the elements in the history vector represent a method that actually occurs in the history, the complexity remains contained as standard linear algebra can be applied in the analysis of the history vector. An example analysis is demonstrated in IIIC using the Python Numpy library.
IiD Formal Definition
The formal definition of quantifiability is described using terminology from mathematics, set theory, and linear algebra in addition to formalisms presented by Herlihy et al. [34]
to describe concurrent systems. Methods are classified according to the following convention. A
producer is a method that generates an item to be placed in a data structure. A consumer is a method that removes an item from the data structure. A reader is a method that reads an item from the data structure. A writer is a method that writes to an existing item in the data structure.A method call set is an unordered set of method calls in a history. A producer set is a subset of the method call set that contains all its producer method calls. A consumer set is a subset of the method call set that contains all its consumer method calls. A writer set is a subset of the method call set that contains all its writer method calls. A reader set is a subset of the method call set that contains all its reader method calls. Since a method call is a pair consisting of an invocation and next matching response, no method in the method call set will be pending. Quantifiability does not discard the pending method calls from the system state nor does it place any constraints on their behavior while they remain pending.
The history vector described in Section IIC is transformed such that the method calls in a history are represented as a set of vectors. To maintain consistency in defining quantifiability as a property over a set of vectors, all method calls are represented as column vectors. Each position of the vector represents a unique combination of input/output parameters and objects that are encountered by the system, where this representation is uniform among the set of vectors. Since quantifiability places no constraints on the methods based on the process calling the method, the process dimension described in Section IIC is omitted. Given a system that encounters unique combinations of input/output parameters and objects, each method call is represented by an dimensional column vector.
The value assignment scheme is chosen such that the changes to the system state by the method calls are “quantified.”
For all cases, let be a column vector that represents method call in a concurrent history. Each element of is initialized to 0.
Case (): Let be the position in representing the combination of input parameters passed to and the object that operates on. Then .
Case (): Let be the position in representing the combination of output parameters returned by and the object that operates on. Then .
Case (): Let be the position in representing the combination of input parameters passed to and the object that operates on. Let be the position in representing the combination of input parameters that correspond to the previous value held by the object that is overwritten by .
If , then and , else .
Case (): Let be the position in representing the combination of output parameters returned by and the object that operates on. Let be a column vector representing a read index for each combination of output parameters returned by a reader method, where each element of is initialized to 0. Then , .
In the case for , setting to 1, where denotes the position representing the combination of input parameters passed to and the object that operates on, captures the entrance of the new item into the system. In the case for , setting to 1, where denotes the position representing the combination of output parameters returned by and the object that operates on, captures the removal of the item from the system.
In the case for , an item that exists in the system is overwritten with the input parameters passed to . A writer method accomplishes two different things in one atomic step: 1) it consumes the previous value held by the item and 2) it produces a new value for the item. This state change is represented by setting the position in representing the corresponding object and combination of the input parameters to be written to an item to 1 and by setting the position in representing the corresponding object and combination of input parameters corresponding to the previous value held by the item to 1. If the input parameters corresponding to the previous value held by the item are identical to the input parameters to be written to an item, then the position in representing the combination of input parameters to be written to the item is set to zero since no change has been made to the system state.
To separate the two actions performed by the writer method into separate vectors, linear algebra can be applied to in the following way. Let be the vector representing the producer effect of the writer method. Then . Let be the vector representing the consumer effect of the writer method. Then .
The addition of to when computing will cause all elements with a 1 value to become 0, and the multiplication of the scalar will revert all elements with a value of 2 back to 1. The floor function is applied to revert elements with a value of back to 0. A similar reasoning can be applied to the computation of .
In the case for , returns the state of an item that exists in the system as output parameters. Multiple reads are permitted for an item with the constraint that the output parameters returned by a reader reflect a state of the item that was initialized by a producer method or updated by a writer method. This behavior is accounted for by setting , where denotes the position representing the corresponding object and the combination of output parameters returned by and represents the read count for the combination of output parameters returned by . The series is a geometric series, where . Since a concurrent history will always contain a finite number of methods, the elements of the resulting vector obtained by taking the sum of the reader method vectors will have a value in the range of . If this vector is further added with the sum of the producer method vectors and the writer method vectors , the elements of the resulting vector will always be greater than zero given that the output of all reader methods corresponds with a value that was either initialized by a producer method or updated by a writer method.
Definition 1.
Let be the vector obtained by applying vector addition to the set of vectors for the producer set of history .
Let be the vector obtained by applying vector addition to the set of vectors for the writer set of history .
Let be the vector obtained by applying vector addition to the set of vectors for the writer set of history .
Let be the vector obtained by applying vector addition to the set of vectors for the reader set of history .
Let be the vector obtained by applying vector addition to the set of vectors for the consumer set of history .
Let be a vector with each element initialized to 0.
For each element ,
if then
= + +
else
= + + + + .
History is quantifiable if for each element , .
Informally, if all vectors representing the methods in the method call set of history are added together, the value of each element should be greater than or equal to zero. This property indicates that the net effect of all methods invoked upon the system is compliant with the requirement that no nonexistent items have been removed, updated, or read from the system. In other words, all method calls are “conserved.” The values of the vectors for the reader method are assigned such that each element in the sum of the reader method vectors is always greater than 1. As long as the output of the reader method is equivalent to the input of a producer method or writer method, then the reader method has observed a state of the system that corresponds to the occurrence of a producer method or writer method. The ceiling function is applied to if which yields a value that is also . Once the reader method vectors have been added appropriately, the remaining method call vectors can be directly added to compute for history .
If any element of is less than zero, then a consume action has been applied to either an item that does not exist in the system state (the item was previously consumed by another consumer method) or an item that never existed in the system state (the item was never produced by a producer method or written by a writer method), which is not quantifiable due to a violation of Principle 1.
A notable difference between defining correctness as properties over a set of vectors and defining correctness as properties of legal sequential histories is the growth rate of a set of vectors versus legal sequential histories when the number of methods called in a history is increased. The size of a set of vectors grows at the rate of with respect to methods called in a history. The number of legal sequential histories grows at the rate of with respect to methods called in a history. This leads to significant time cost savings when verifying a correctness condition defined as properties over a set of vectors since analysis of dimensional vectors using linear algebra can be performed in time ( time to assign values and compute separate vectors that each represent a sum of the producer, consumer, writer, and reader method call vectors, and time to add the vectors representing the sum of the producer, consumer, writer, and reader method call vectors).
Iii Verification Algorithm
Algorithm 1 presents the type definitions for a method call in a history. Each Method type has a field for its OpType (producer, consumer, reader, or writer) (line 1.7), a field for the object that the method is invoked upon (line 1.8), a field for its input parameters (line 1.9), a field for its output parameters (line 1.10), and a field for a previous value if the method is a write (line 1.11). The function ParamsToIndex on line 1.12 is a hashing function that retrieves in time the unique index associated with the object and input/output parameters. Each method type has a field for its classification (producer, consumer, reader, or writer), a field for the object that the method is invoked upon, a field for its input parameters, a field for its output parameters, and a field for a previous value if the method is a write. The function ParamsToIndex is a hashing function that retrieves in time the unique index associated with the object and input/output parameters.
Algorithm 2 presents the verification algorithm for quantifiability derived from the corresponding formal definition. Line 2.1 is defined as a constant that is the total number of unique configurations comprising the input/output and objects that are encountered by the system. The array tracks the running sum of the producer method vectors. The array tracks the running sum of the new values written by the writer method vectors. The array tracks the running sum of the previous values overwritten by the writer method vectors. The array tracks the running sum of the reader method vectors. The array tracks the running sum of the consumer method vectors. The array tracks the running sum of the read index for the reader method vectors. The array tracks the final sum of all method call vectors. The VerifyHistory function accepts a set of method calls as an argument on line 2.3. The forloop on line 2.5 iterates through the methods in the method call set and adds a value to the appropriate array according to the value assignment scheme discussed in Section IID.
The forloop on line 2.21 iterates through each of the configurations and sums the method call vectors according to Definition 1 to obtain the final vector . If any element of is less than zero or greater than one (line 2.26), then the history is not quantifiable. Otherwise, if all elements of are greater than or equal to zero, then the history is quantifiable.
Iiia Time Complexity of Verification Algorithm
Let be the total number of methods in a history and let be the total number of configurations determined according to the input/output of each method and the object to be invoked on by the method. The forloop on line 2.5 takes time to iterate through all methods in the method call set. The forloop on line 2.5 takes time to iterate through all possible configurations. Let be the total number of input/output combinations and let be the total number of objects. The total number of configurations is . Therefore, the total time complexity of VerifyHistory is .
IiiB Correctness of Verification Algorithm
The correctness arguments for VerifyHistory are provided to convince the reader that representing a history as a set of method call vectors and applying linear algebra to the vectors to compute guarantees that if each element of is greater than or equal to zero, then the methods of appear to occur atomically in some arbitrary order.
Theorem 1.
Let be a history. Let be an dimensional vector, where is the total number of configurations comprising the object and input or output parameters of the method calls of . Let vector represent the final state of the system resulting from history . Let be an arbitrary item configuration that is a unique combination of the object and input/output method call parameters. VerifyHistory produces the vector such that if and only if history corresponds to an arbitrary permutation of the methods in the method call set such that the methods appear to occur atomically.
Proof.
Since no restrictions are placed on the producer methods (duplicate items are permitted), it is sufficient to show that the methods in a history appear to occur atomically as long as no nonexistent items have been removed, updated, or read from the system.
Each element of is greater than or equal to one if an item with the configuration associated with position exists in the system, or zero if if an item with the configuration associated with position does not exist in the system.
First, it must be shown that if then history corresponds to an arbitrary permutation of the methods in the method call set such that the methods appear to occur atomically.
By contrapositive proof, suppose that history corresponds to an arbitrary permutation of the methods in the method call set such that the methods do not appear to occur atomically (i.e., a nonexistent item has either been removed from the system, updated in the system, or read from the system).
Proceed using a proof by cases evaluation.
Case 1: A nonexistent item has been removed from the system. If a nonexistent item is removed from the system, then ( + ) + . Since and are always less than or equal to zero, , so is false.
Case 2: A nonexistent item has been updated in the system. If a nonexistent item is updated in the system, then ( + ) + . Since and are always less than or equal to zero, , so is false.
Case 3: A nonexistent item has been read in the system. If a nonexistent item is read from the system, then ( + ) and the ceiling function cannot be applied. Since , and and are always less than or equal to zero, , so is false.
Second, it must be shown that if history corresponds to an arbitrary permutation of the methods in the method call set such that the methods appear to occur atomically then .
By contrapositive proof, suppose that .
The only elements that could cause to be less than zero are , , and .
Proceed using a proof by cases evaluation.
Case 1: , , and . If , then ( + + ) .
Isolating to one side of the inequality yields ( + ), which implies that a nonexistent item has been removed from the system, so the statement “the methods appear to occur atomically” is false.
Case 2: , , and . If , then ( + + ) .
Isolating to one side of the inequality yields ( + ), which implies that a nonexistent item has been updated in the system, so the statement “the methods appear to occur atomically” is false.
Case 3: , , and .
If , then ( + + ) .
Since is guaranteed to fall in the range of , the inequality ( + + ) will only be true if ( + ) .
This implies that a nonexistent item has be read from the system, so the statement “the methods appear to occur atomically” is false.
∎
IiiC Correctness with Linear Algebra
The mathematics above is readily translated into programming languages optimized for linear algebra, such as Python. To facilitate certain computations a history vector may be reshaped into tensors of rank 2, 3, or 4. Reshaping makes it convenient to project the history over one or more components and calculate properties of the history.
To determine quantifiability using Algorithm 2, the history vector is folded into a tensor of rank 2 and summed over the methods and the processes, thereby reduced to the axes of objects and items. the ceiling and floor functions are applied and quantifiable correctness of the history verified using a few lines of Numpy code.
For example consider the sample history vector below of two methods, two processes, two objects and four items.
Iv Properties of Quantifiability
The system model presented in Section IIB is mapped to a vector space. We do not claim that the axioms of a vector space hold for all possible concurrent systems. We do propose a mapping from many types of concurrent systems to the mathematical ideal of a vector space. Concurrent systems fitting the model define a vector space and their histories are the vectors in that space. For concurrent systems fitting the model properties of a vector space become axiomatic and have a variety of uses.
Iva Vector Space Properties
This section interprets vector space properties as applied to concurrent histories. Quantifiable histories, closed under addition and scalar multiplication, are a subspace of these histories.

[style=unboxed,leftmargin=0cm,itemsep=1pt,parsep=2pt]
 Closure under addition

 Adding two quantifiable histories (i.e. whose sum over ObjectItem pairs is nonnegative) produces another history with the same property.
 Addition is associative and commutative

 The quantifiable system model allows any order and grouping of multiple histories as they are added together.
 Zero history

 The system model admits a history consisting of all zeroes, in which nothing happens.
 Inverse histories

 An inverse history is one that cancels all the operations of a previous history, following quantifiability Principle 1.
 Closure under scalar multiplication

 A quantifiable history may be repeated any number of times and the resulting history remains quantifiable.
 Scalar multiplication is distributive

 Scalar multiplication is repeating a history. Repeating multiple histories together or individually the same number of times sums to the same values representing the final state of the system, following quantifiability Principle 1 and Principle 2.
 Identity scalar

 The identity scalar is one. A history multiplied by one remains the same vector.
IvB Vector Norms
Vector norms are scalar quantities that can be assigned meaning to concurrent systems that define a vector space.
IvB1 Taxicab Norm
The taxicab norm, also called the norm, represents the operation count. The norm is interpreted based on the value assignment scheme. In general it measures the amount of work done on the objects in the system during the history. The norm of a history resembles the potential function used in amortized complexity analysis [55].
(4) 
If the history vector is transformed into a set of vectors {,…,…}, where is the total number of objects in the system state and where only includes the producer methods for object , the taxicab norm of yields the maximum size of object . This information is necessary to verify that a method that observes the size of an object is quantifiable. Since quantifiability is compositional, these methods can be reasoned about separately from the other methods in a history. Let size() be a method that returns the size of object . Let the value assigned to size() be the value returned by size(). The method size() is quantifiable if size() . The reasoning for determining the quantifiability of size() in this manner is that size() should never return a value greater than the total number of items produced for object .
Let is_empty() be a method that returns true if the size of object is zero and false otherwise. Let the value assigned to is_empty() be 1 if is_empty() returns true and zero otherwise. The method is_empty() is quantifiable if (is_empty() == 1) or (is_empty() == 0 and 0). The reasoning for determining the quantifiability of is_empty() in this manner is that is_empty() could return true for any circumstance and be quantifiable under the assumption that object is initially empty. However, if is_empty() returns false, then at least one item must have been produced for object , which is verified by checking that 0.
IvB2 Euclidean Distance
The Euclidean distance, i.e. length or norm of a history vector, is the distance from the initial to the final state in the vector space defined by the concurrent system.
(5) 
IvB3 Floor of Sum
Although not a vector norm (because it may have a negative value), the floor of the sum of the history vector reveals net versus for the history. This is height of the stack in the final state, possibly a negative stack as in Figure 1.
(6) 
IvC Compositionality
To show compositionality, it must be shown that the composition of two quantifiable histories is quantifiable, and that the decomposition of histories, i.e. the projection of the history on any of its obejcts, is also a quantifiable history. This is formally stated in the following theorem.
Theorem 2.
History is quantifiable if and only if, for each object , is quantifiable.
Proof.
It first must be shown that if each history for object is quantifiable, then history is quantifiable. Since the addition of quantifiable histories is closed under addition, it follows that the composition of quantifiable object subhistories is also quantifiable. Therefore, is quantifiable.
It now must be shown that if history is quantifiable, then each history for object is quantifiable. Since is quantifiable, then each element of the vector . Each position in corresponds to a unique configuration representing the input/output of a method and the object that the method is invoked upon. Since each element of the vector , then for each element associated with object , . Each history for object is therefore quantifiable. ∎
IvD NonBlocking and NonWaiting Properties
A correctness condition may inherently cause blocking, as is the case with serializability applied to transactions [34]. Quantifiability shares with linearizability the nonblocking property, and for the same reason: it never forces a process with a pending invocation to block.
The requirement that all methods must succeed or be explicitly cancelled raises the question of how this is nonblocking. Indeed a thread might choose to block if there is no way it can proceed without the return value or the state change resulting from the method. It is a matter for the application to decide, not an inherent property of Quantifiability Principle 1. For example, consider thread 1 calling a method on a concurrent stack, . This can be written as <Type> v = s.pop(); which is blocking in C. Or it may be invoked as a call by reference in the formal parameters s.pop(<Type> &v); which is nonblocking. The second invocation also permits the thread to block if desired by spinning on the address to check if a result is available. If address &v is not pointing to a value of <Type>, the method has not yet succeeded. Alternatively, instead of spinwaiting, a thread can do a “context switch” and proceed with other operations while waiting for the pending operation to succeed. And if the thread decides the method is no longer needed, it can be cancelled.
Duplicate method calls can be handled in several ways conforming to Principle 1. A duplicate call might be considered a syntactic shorthand for “cancel the first operation and resubmit”, or it could throw a run time error to have identical calls on the same address. Alternatively an index could be added to the method call to uniquely identify it such that it can be distinguished from other identical calls. Quantifiabile semantics support these options in blocking and nonblocking versions well suited to the type systems of programming languages. Useful extensions of this include permitting threads to prerequest some number of method calls, and the ability to assign priority to the method call independent of the order represented in the data structure. For example the priority queue demonstrated in this paper implements the priority of items in the queue and the priority of incoming requests. The proof of the nonblocking property for quantifiability is presented below.
IvE Proof of NonBlocking Property
The following proof shows that quantifiability is a nonblocking property; that is, it is never required to wait for another pending operation to complete.
Theorem 3.
Let be an invocation of a method . If is a pending invocation in a quantifiable history with a corresponding vector , then either there exists a response such that either is quantifiable or is quantifiable.
Proof.
If method is a producer method that produces item with configuration , then there exists a response such that is quantifiable because is greater than zero since by the definition of quantifiability. If method is a consumer method that consumes an item with configuration , then a response exists if . If method is a writer method that updates item with configuration to a new configuration , then a response exists if and . If method is a reader method that reads an item with configuration , then a response exists if . If a response for method does not exist, then method can be cancelled. Upon cancellation, is removed from history . Since quantifiability places no restrictions on the behavior of pending method calls, is quantifiable. ∎
V Related Work
Quantifiability is motivated by recent advances in concurrency research. Frequently cited works [56, 57] are already moving in the direction of the two principles stated in Section IIA. This section places quantifiability in context of several threads of research: the basis of concurrent correctness conditions, complexity of proving correctness and performance of data structures.
Va Relationship to Other Correctness Conditions
A sequential specification for an object is a set of sequential histories for the object [35]. A sequential history is legal if each subhistory in for object belongs to the sequential specification for . Many correctness conditions for concurrent data structures are proposed in literature [32, 33, 34, 35, 36, 12, 37], all of which reason about concurrent data structure correctness by demonstrating that a concurrent history is equivalent to a legal sequential history.
Reasoning about concurrent data structures in terms of a legal sequential history is appealing because it builds upon previous techniques for verifying correctness of sequential objects [38, 39]. However, generating all possible legal sequential histories of a concurrent history has a worst case time complexity of , leading to inefficient verification techniques for determining correctness of concurrent data structures.
Serializability [32] is a correctness condition such that a history is serializable if and only if there is a serial history such that is equivalent to . A history is strictly serializable if there is a serial history such that is equivalent to , and an atomic write ordered before an atomic read in implies that the same order be retained by . Papadimitriou draws conclusions implying that there is no efficient algorithm that distinguishes between serializable and nonserializable histories [32]. A decision problem is a problem that is a yesno question of the input values. A decision problem is in the Nondeterministic Polynomial time (NP) complexity class if “yes” answers to the decision problem can be verified in Polynomial time (P). A decision problem is in NPcomplete if is in NP, and every problem in NP is reducible to in polynomial time. Papadimitriou proved that testing whether a history is serializable is NPcomplete. An implication of this result is that, unless , there is no efficient algorithm that distinguishes between serializable and nonserializable histories [32].
Sequential consistency [33] is a correctness condition for multiprocessor programs such that the result of any execution is the same as if the operations of all processors were executed sequentially, and the operations of each individual processor appear in this sequence in program order. Lamport indicates that the sequential consistency of an individual process does not guarantee that the program as a whole is sequentially consistent. Lamport proposes that compositionality for sequentially consistent objects can be achieved by requiring that the memory requests from all processors issued to an individual memory module are serviced from a single FIFO queue which guarantees a total ordering for the program execution. Since sequential consistency is not compositional, Lamport proposes that compositionality for sequentially consistent objects can be achieved by requiring that the memory requests from all processors are serviced from a single FIFO queue. However, a single FIFO queue is a sequential bottleneck that limits the potential concurrency for the entire system.
Linearizability [34] is a correctness condition such that a history is linearizable if is equivalent to a legal sequential history, and each method call appears to take effect instantaneously at some moment between its invocation and response. Herlihy et al. [35] suggest that linearizability can be informally reasoned about by identifying a linearization point in which the method call appears to take effect at some moment between the method’s invocation and response. Identifying linearization points avoids reference to a legal sequential history when reasoning about correctness, but such reasoning is difficult to perform automatically. Herlihy et al. [34] compared linearizability with strict serializability by noting that linearizability can be viewed as a special case of strict serializability where transactions are restricted to consist of a single operation applied to a single object. Comparisons of correctness conditions for concurrent objects to serializability are made in a similar manner by considering a special case of serializability where transactions are restricted to consist of a single method applied to a single object. Herlihy compared linearizability with serializability by grouping multiple method calls into a transaction making the histories strictly serializable. Here we consider the serializability of method calls, not transactions.
Quiescent consistency [36] is a correctness condition for counting networks that establishes a safety property for a network of twoinput twooutput computing elements such that the inputs will be forwarded to the correct output wires at any quiescent state. A step property is defined to describe a property over the outputs that is always true at the quiescent state. Quasilinearizability [12] builds upon the formal definition of linearizability to include a sequential specification of an object that is extended to a larger set that includes sequential histories that are not legal, but are within a bounded distance from a legal sequential history.
Ou et al. [37] present nondeterministic linearizability, a correctness model for concurrent data structures that utilize the relaxed semantics of the C/C++ memory model. A specification for a concurrent object comprises a nondeterministic, sequentialized version of the concurrent object and an admissibility function that permits two methods to be unordered in the concurrent execution. A valid sequential history is a sequential history that is legal in the C/C++ memory model. The notion of justified behaviors is introduced to account for method calls exhibiting nondeterministic behavior in a concurrent history. A concurrent object is nondeterministic linearizable on a valid sequential history for a specification if and only if each method call in is justified and returns a value as specified by its nondeterministic specification. A concurrent object is nondeterministic linearizable on an execution for a specification if and only if is nondeterministic linearizable for all valid sequential histories of for .
Unlike the correctness conditions proposed in literature, quantifiability does not define correctness of a concurrent history by referencing an equivalent legal sequential history. Quantifiability requires that the method calls be conserved, enabling correctness to be proven by quantifying the method calls and applying linear algebra to the method call vectors. This fundamental difference enables quantifiability to be verified more efficiently than the existing correctness conditions because applying linear algebra can be performed in + time ( is the number of methods and is the number of input/output and object configurations), while deriving the legal sequential histories has a worst case time complexity of . A diagram illustrating the relationship between quantifiability and the correctness conditions
Herlihy et al. [34] compared linearizability with strict serializability by noting that linearizability can be viewed as a special case of strict serializability where transactions are restricted to consist of a single operation applied to a single object. Comparisons of correctness conditions for concurrent objects to serializability are made in a similar manner by considering a special case of serializability where transactions are restricted to consist of a single method applied to a single object. Figure 1 shows the relationship of linearizability, sequential consistency, quiescent consistency, and quantifiability, all of which are serializable due to the requirement that all method calls should appear to occur atomically. All linearizable histories are sequentially consistent because the preservation of realtime order provided by linearizability also preserves program order. All linearizable histories are also quiescently consistent because method calls always take effect according to realtime order regardless of periods of quiescence. Quiescent consistency and sequential consistency are incomparable [35], so there exist sequentially consistent histories that are not quiescently consistent. Similarly, there exist quiescently consistent histories that are not sequentially consistent. There exist linearizable, sequentially consistent, quiescently consistent, and serializable histories that are not quantifiable because these correctness conditions all allow method calls to return null when reaching an undefined object state. There exist quantifiable histories that are not linearizable, sequentially consistent, or quiescently consistent because quantifiability places no constraints on realtime order or program order of method calls.
VB Proving Correctness
Verification tools are proposed [40, 58, 59, 37] to enable a concurrent data structure to be checked for correctness according to various correctness conditions. Vechev et al. [40] present an approach for automatically checking linearizability of concurrent data structures. The approach incorporates two methods for checking linearizability: 1) automatic linearization and 2) linearization points. The automatic linearization technique searches for a permitted linearization by exploring all permutations of a concurrent execution. The linearization point technique accepts userprovided annotations indicating the linearization points of an algorithm so that an order between overlapping method calls can be determined, eliminating the need to explore all permutations of a concurrent execution. Burckhardt et al. [58] present LineUp, a tool that checks deterministic linearizability automatically. Lineup derives a sequential specification for a concurrent data structure by recording all sequential histories for a finite test and checking that each concurrent execution is equivalent to one of the recorded sequential histories. Zhang et al. [59] present Roundup, a runtime verification tool for checking quasilinearizability violations of concurrent data structures. Roundup systematically executes a test program with a standard sequential data structure to compute all possible legal sequential histories. Each concurrent history is first checked for linearizability by determining if it is equivalent to a legal sequential history. If the concurrent history is linearizable, it is also quasilinearizable by definition. Otherwise, the order of the method calls in each legal sequential history are rearranged up to a specified distance to generate a quasilinearization in which the concurrent history is compared with to determine if it is quasilinearizable. Ou et al. [37] develop a tool that checks nondeterministic linearizability for concurrent data structures designed using the relaxed semantics of the C/C++ memory model. Ordering point annotations are utilized to identify a specific instruction in which the effects of a method become visible to the system. During model checking, the method calls are organized in a directed acyclic graph where a method has a directed edge to if ’s ordering point occurs before ’s ordering point in the generated concurrent history. A topological sort of the directed acyclic graph yields a legal sequential history for the concurrent history. To reduce the large time complexity required to derive all possible legal sequential histories, an option is provided to the user that randomly generates and checks a customized number of legal sequential histories.
These verification tools are all faced with the computationally expensive burden of generating all possible legal sequential histories of a concurrent history since this is the basis of correctness for the correctness conditions in literature. The correctness verification tools presented by Vechev et al. [40] and Ou et al. [37] accept user annotated linearization points to eliminate the need for deriving a legal sequential history for a reordering of overlapping methods. Although this optimization is effective for fixed linearization points, it could potentially miss valid legal sequential histories for method calls with nonfixed linearization points in which the linearization point may change based on overlapping method calls.
VC Performance of Related Data Structures
The elimination backoff stack (EBS) [57] uses an elimination array where and method calls are matched to each other at random within a short time delay if the main stack is suffering from contention. This concept of waiting to match up mutually satisfying calls provided inspiration for quantifiability. In the algorithm, the delay is set to a fraction of a second, which is a sufficient amount of time to find a match during busy times. If no match arrives, the method call retries its operation on the central stack object. When operating on the central stack object, the method is at risk of failing if the stack is empty. However, if the elimination array delay time is set to infinite, the elimination backoff stack implements Quantifiability Principle 1, and all the method calls wait until they succeed.
Dual data structures [60] are concurrent object implementations that hold reservations in addition to data to handle conditional semantics. Partial methods are divided into a request method and a followup method. If the dual data structure is empty, a request is inserted into the data structure to be fulfilled by another thread. The followup method is used to determine if the request has been fulfilled. Dual data structures are linearizable and can be implemented to provide nonblocking progress guarantees including lockfree, waitfree, or obstructionfree. The main difference between dual data structures and quantifiable data structures is the allowable order in which the requests may be fulfilled. The relaxed semantics provided by quantifiability provides an opportunity for performance gains over the dual data structures.
The TSQueue [56] is one of the fastest queue implementations, claiming twice the speed of the elimination backoff version. The TS Queue also relies on matching up method calls, enabling methods that would otherwise fail when reaching an undefined state of the queue to instead be fulfilled at a later time. In the TS Queue, rather than a global delay, there is a tunable parameter called padding
added to different method calls. By setting an infinite time padding on all method calls, the TS Queue follows Quantifiability Principle 1.
These works share in common that they significantly improve performance by using a window of time in which pending method calls are conserved until they can succeed. Quantifiability Principle 1 extends this window of time for conservation of method calls indefinitely, while allowing threads to cancel them as needed for specific applications.
The FIFO queue [61] maintains segments each consisting of slots implemented as either an array for a bounded queue or a list for an unbounded queue. This design enables up to and operations to be performed in parallel and allows elements to be dequeued outoforder up to a distance . Quantifiability takes the relaxed semantics of the FIFO queue a step further by allowing method calls to occur outoforder up to any arbitrary distance, leading to significant performance gains as demonstrated in Section VID.
Vi Implementation
Quantifiable containers must conserve method calls. Figure 2 shows a simple LIFO stack implementing quantifiability. As first class members of the system, pending method calls can be included in a data structure. Items in a stack are the result of previous calls to . Quantifiability requires that calls to on an empty stack have equal standing to be included in the structure. Intuitively, the unsatisfied calls to form a stack of negative length, where the methods will be satisfied as resources permit.
The stack is initialized with a pointer to an empty sentinel node. Three calls to load the items 5,9,7 to the stack @time1, followed by five calls resulting in a negative stack @time2. The unfulfilled calls to are first class objects saved to the stack. The next will send an item to and promote to the top of the stack. The calls to are pending and nonblocking. Processes that create the pending calls may periodically check the address. They may also send a request to cancel if it is no longer of interest. The order in which calls are fulfilled is implementation dependent. The stack may include relaxed semantics [12]. It may have arbitrary prioritization, such as execute calls from processes paying higher rates [62].
Quantifiability is applicable to other abstract data types that deliver additional functionality beyond the standard producer/consumer methods provided by queues and stacks. Consider a reader method such as a operation for a vector or a operation for a set. If the item to be read does not exist in the data structure, a pending item is created and placed in the data structure at the same location where the item to be read would be placed if it existed. If a pending item already exists for the item to be read, the reader method references this pending item. Once a producer method produces the item for which the pending item was created, the pending item is updated to a regular (nonpending) item. Since the reader methods hold a reference to this item, they may check the address when desired to determine if the item of interest is available to be read. A similar strategy can be utilized for writer methods.
The concept of retrieving an item to be fulfilled at a later time is implemented in C++11, C#, and Java as promises and futures [63]. The wait_for function in C++ is provided by the future object that enables a thread to wait for the item in the promise object to be set to a value for a specified time duration. Once the wait_for function returns a ready status, the item value can be retrieved by the future object through the get function. The disadvantage of the get function is that it blocks until the item value is set by the promise object. Once the get function returns the item, the future object is no longer valid, leading to undefined behavior if other threads invoke get on this future object. Due to the semantics of the get function, we advise implementing retrieval of an item to be fulfilled at a later time with a shared object used to announce information, referred to as a descriptor object [64, 65]. A discussion on progress guarantees is provided below.
Via Progress
The length of time a method call may remain pending is a progress condition. For example wait free progress requires that the method call would succeed in a finite amount of time. The idea that method calls can happen at the same time is not so hard to accept because they may fulfill each other, in the manner of the elimination backoff stack [57]. Accepting that they may happen in any order is to accept the reality that since the days of the shared bus are gone, there is nothing like an “external observer” to determine if a history is acceptable. Latency is a fact of parallel and distributed systems. Quantifiable systems may include synchronization schemes, important in many domains, but the correctness condition should be that methods succeed, whatever may be done to give an illusion of realtime ordering.
Lockfreedom is the property that some thread is guaranteed to make progress. Waitfreedom is the property that all threads are guaranteed to make progress. Quantifiable data structures can be implemented in a way that satisfies either lockfreedom or waitfreedom. The strategies for guaranteeing lockfreedom or waitfreedom in quantifiable data structures is no different than nonblocking data structures presented in literature [57, 66, 67, 68]. Quantifiability is compatible with the existing synchronization methods for lockfreedom and waitfreedom because it is a nonblocking correctness property.
As discussed in Section IVD, a thread can proceed with other operations while waiting on a pending operation to succeed. This can be achieved by using a descriptor object to create a pending item to be fulfilled by another thread. Consider a consumer method such as a operation for a hashmap. If the item to be read does not exist in the hashmap, a pending item is created and placed in the hashmap at the same location where the item to be read would be placed if it existed. If a pending item already exists for the item to be read, the reader method references this pending item. Once a producer method produces the item for which the pending item was created, the pending item is updated to a regular (nonpending) item. Since the reader methods hold a reference to this item, they may check the address when desired to determine if the item of interest is available to be read. Consider a reader method such as a operation for a vector. If the item to be read does not exist in the data structure, a pending item is created and placed in the data structure at the same location where the item to be read would be placed if it existed. If a pending item already exists for the item to be read, the reader method references this pending item. Once a producer method produces the item for which the pending item was created, the pending item is updated to a regular (nonpending) item. Since the reader methods hold a reference to this item, they may check the address when desired to determine if the item of interest is available to be read. This enables quantifiability to be achieved in a lockfree manner. A similar strategy can be utilized for consumer and writer methods. Waitfreedom can also be achieved using helping schemes in conjunction with descriptor objects to announce an operation to be completed in a table such that all threads are required to check the announcement table and help a pending operation prior to starting their own operation [69].
Lockfreedom is achieved through the atomic instruction CompareAndSwap (CAS). CAS accepts as input a memory location, an expected value, and an update value. If the data referenced by the memory location is equivalent to the expected value, then the data referenced by the memory location is changed to the update value and true is returned. Otherwise, no change is made and false is returned. Since CAS will only fail if another thread successfully updates the data referenced by the memory location, a failed CAS implies that some other thread made progress. Thus, lockfreedom can be achieved through a noncyclical CASbased loop.
Transactions require multiple atomic steps to complete [70, 71]. For these circumstances, a descriptor object can be used to post the required atomic steps for an operation and a helping scheme can be employed to enable other threads to help complete the pending operation in a lockfree manner. Waitfreedom is also achieved using helping schemes in conjunction with descriptor objects to announce an operation to be completed in a table such that all threads are required to check the announcement table and help a pending operation prior to starting their own operation [69].
ViB QStack
The quantifiable stack (QStack) is designed to avoid contention wherever possible. Consider the state of a stack receiving two concurrent operations. Assume a stack contains only the node . Two threads concurrently push nodes and . The state of the stack after both operation have completed is shown in figure 5. The order is one of two possibilities: , or . Under quantifiability, either or are valid candidates for a operation.
The QStack is structured as a doublylinked tree of nodes. Two concurrent method calls are both allowed to append their nodes to the data structure, forming a fork in the tree (Fig. 5). and are allowed to insert or remove nodes at any “leaf" node. To facilitate this design we add a descriptor pointer to each node in the stack. At the start of each operation, a thread creates a descriptor object with all the details necessary for an arbitrary thread to carry out the intended operation.
Algorithm 3 contains type definitions for the QStack. contains the elements , , and . represents the abstract type being stored in the data structure. identifies the node as either a pushed value or and unsatisfied pop operation. is an array holding references to the children of the node, while contains a reference to its parent. contains the same and variables, as well as . designated whether the associated operation for the descriptor object is currently pending, or if the thread performing that operation has completed it. The stack data structure has a global array , which contains all leaf nodes in the tree.
In order to conserve unsatisfied pops, we generalize the behaviour of and operations with and . If a is made on an empty stack, we instead begin a stack of waiting operations by calling and designating the inserted node as an unfulfilled operation. Similarly, if we call on a stack that contains unsatisfied pops, we instead use to eliminate an unsatisfied operation, which then finally returns the value provided by the incoming .
Comments
There are no comments yet.