From this, I understand that
Linearizability and Sequential Consistency are for Single Operation on Single Object
Serializability and Strict Serializability are for Multiple Operations on Multiple Objects.
Does single operation on single object mean the following?
Sequence 1:
T1:
Read(X) Write(X)
T2:
Write(X) Read(X)
Whereas multiple operations and multiple objects mean:
Sequence 2:
T1:
Read(Y) Write(X) Read(Y)
T2:
Write(Y) Read(X) Write(Y)
Does this also mean that:
Sequence 2 can reason about the execution of functions (or methods in java)?
Sequence 1 can ONLY reason about a subset of operations on the same object within the function?
First of all, note that consistent histories---according to a given consistency model, such as linearizability or serializability---are defined as those that satisfy sequential specifications of objects with respect to the points of operation (or method) invocations [1, 2].
More specifically:
sequential specification for an object is just a set of sequential histories for the object
where history is defined as:
a finite sequence of method invocation and response events
where pair of invocation and response events form a method call, i.e.:
a method call in a history H is a pair consisting of an invocation and the next matching response in H
Thus, indeed, linearizability is defined for single operations on single objects, but the definition can covers histories over multiple objects as well [1, 2]. On the other hand, serializability covers histories with multiple operations on multiple objects inherently, since it is (usually) defined in the context of transactional processing, where each transaction can involve multiple operations on multiple objects.
The two, namely single operations on single objects and multiple operations on multiple objects, can be made equivalent through defining methods on objects (that potentially unify multiple objects) that define appropriate sequential specification in which whole transactions are viewed as methods on some objects (e.g. a simple example is queue enqueue/dequeue [1]). This, in turn, defines the sequential specification of such objects, and thus valid histories according to given consistency models.
Now, if T1 and T2 are interpreted as threads, then indeed, sequence 1 invokes operations on single object, while sequence 2 clearly invokes multiple operations and multiple objects. Note that in both cases, histories can be checked with respect to both the linearizability and serializability, while read and write operations might be grouped into "semantically" atomic methods as discussed previously.
As per relationship between linearizability and serializability, serializability is strictly weaker consistency model in which valid histories might be equivalent to executions of methods in arbitrary sequences, while linearizability can be viewed as equivalent to strict serializability (if we view transactions as methods of a single object). Note that some authors started using linearizability in the context of transactions [3].
[1] The Art of Multiprocessor Programming. By Nir Shavit and M P. Herlihy
[2] Linearizability: A Correctness Condition for Concurrent Objects. By M P. Herlihy and J M. Wing. ACM Transactions on Programming Languages and Systems, Vol. 12, No. 3, July 1990.
[3] Software Transactional Memory. By Nir Shavit and Dan Touitou. Distrib. Comput. (1997) 10: 99-116.
Related
How can I model two parallel threads that perform operations on an object in a sequence diagram?
In a sequence diagram, a lifeline represents an individual participant in the interaction. So your object that is shared between the threads should appear once and only once in the diagram.
You would also represent with a lifeline each threaded object that interact with your shared object. It could be thread instantiations directly, or it could be several objects that are created in the context of the two threads.
But this is not sufficient. In principle, the sequence of the interactions with your object is indicated by the vertical order of the messages. So how to show that interactions may happen in parallel ?
For representing parallelism, you would use a combined fragment introduced by the operator par. Graphically (see link), the combined fragment is represented as a boxed region in your sequence diagram, parallel sequences being separated by horizontal dashed lines (each of the slices would then correspond to one thread of execution.
P.S: this example shows 2 threads, but you can add more threads by adding more horizontal dashed lines.
Ponylang is a new language that is lock-free and datarace-free. My impression is that to accomplish this, Ponylang looks at the sentence "if two threads can see the same object, then writes must prohibit any other operation by another thread", and uses a type system to enforce the various special cases. For example, there's a type descriptor that says, "no other thread can see this object", and one that says, "this reference is read-only", and various others. Admittedly my understanding of this is quite poor, and ponylang's documentation is short on examples.
My question is: are there operations possible with a lock-based language that aren't translatable into ponylang's type-based system at all? Also, are there such operations that are not translatable into efficient constructs in ponylang?
[...] are there operations possible with a lock-based language that aren't translatable into ponylang's type-based system at all?
The whole point with reference capabilities, in Pony, is to prevent you from doing things that are possible and even trivial, in other languages, like sharing a list between two threads and add elements to it concurrently. So, yes, in languages like Java, you can share data between threads in a way that is impossible in Pony.
Also, are there such operations that are not translatable into efficient constructs in ponylang?
If you're asking if the lock-based languages can be more efficient in some situations, than pony, then I think so. You can always create a situation that benefits from N threads and 1 lock and is worse when you use the actor model which forces you to pass information around in messages.
This thing is not to see the actor model as superior in all cases. It's a different model of concurrency and problems are solved differently. For example, to compute N values and accumulate the results in a list:
In a thread-model you would
create a thread pool,
create thread-safe list,
Create N tasks sharing the list, and
wait for N tasks to finish.
In an actor-model you would
create an actor A waiting for N values,
create N actors B sharing the actor A, and
wait for A to produce a list.
Obviously, each task would add a value to the list and each actor B would send the value to actor A. Depending on how messages are passed between actors, it can be a slower to send N values than to lock N times. Typically it will be slower but, on the other hand, you will never get a list with an unexpected size.
I believe it can do anything that a shared everything + locks can do. with just iso objects and consume it is basically pure a message passing system which can do anything that a lock system does. As in mach3 can do anything linux can.
I have a couple of question about refs and atom, and clojure reference types in general after reading clojure programming and mostly the question is related to this book.
First:
The books says about coordination, and it says "A coordinated operation is one where multiple actors must cooperate in order to yield correct results.". Does this mean if I have 3 fn fn1, fn2, and fn3, and each of them does some operation that possibly change the state of the reference (assuming it happens in each own Thread), it happens in a synchronous way in a chained operation? Something like, output of fn1 is input of fn2 and so on.
Second:
I cannot understand the difference between refs and atoms. The book says refs is for coordinated sync and atoms is for uncoordinated sync. Each of them (refs and atoms) has their own example, where atoms is used in such way where it is being operated by multiple function (1 atom 2 function), and multiple refs with 1 function. The book didn't give an example why we shouldn't or can't do the other way around.
Atoms allow multiple threads to apply transformations to a single value and guarantee the transformations are atomic. swap! takes the atom and a function expecting the current value of the atom. The result of calling that function with the current value is stored in the atom. multiple calls to swap! may interleave, but each call will run in isolation.
Refs allow multiple threads to update multiple values in a co-ordinated way. All updates to all refs inside a sync will complete or none will. You MUST write your code such that transaction retries are catered for. There are a few potential performance tweaks, if you can relax the ordering of operations, which MAY reduce the chance of transaction retry (but don't guarantee it).
Difference is really easy.
refs are operating under a transaction (similar to Databases transactions). Imagine a banking system. You can represent an account as a ref.
To transfer money, you start a Clojure STM transaction -via (dosync)-. Subtract X amount of money from ref-1 and add that amount to account ref-2.
If something goes wrong, then Clojure STM will restart the operation.
Imagine there is no transaction. You subtracted X amount of money from ref-1 and before you add that amount to ref-2, something went wrong in your system. Your customers will not be happy at all (if you aren't sued any way).
The Clojure STM is implemented as MVCC.
Atoms on the other hand don't need a transaction in place to operate. Atoms are convenient when there is no coordination. For example, a counter that increases the total number of a visited page in a web analytics system.
Have a look at Clojure Refs. It offers a lot of valuable information.
A forum post incidates using uniqueness types instead of STM. I don't understand what it is saying. How is uniqueness types suppose to deal with the problem that STM is trying to deal with where multiple threads are updating the same variable for example?
I've looked at wikipedia's articles on uniqueness types and linear types and its still not clear what the forum post meant.
Designing systems where data is shared and mutated concurrently by multiple threads is hard.
Approaches to make concurrency easier include:
STM -- With STM, data can still be shared and mutated by multiple threads, but concurrent mutations are detected thanks to the use of transactions.
Uniqueness types -- With uniqueness types, at most one reference to an object exists. So, by definition, it is impossible to mutate the same data concurrently (you would need two references at least, one per thread).
Immutability -- Avoid the problem of concurrent mutations altogether and share only immutable data.
Actors -- Actors rely on asynchronous messages, and serialize the messages they receive, thus avoiding concurrent modifications.
Don't quite understand determinism in the context of concurrency and parallelism in Haskell. Some examples would be helpful.
Thanks
When dealing with pure values, the order of evaluation does not matter. That is essentially what parallelism does: Evaluating pure values in parallel. As opposed to pure values, order usually matters for actions with side-effects. Running actions simultaneously is called concurrency.
As an example, consider the two actions putStr "foo" and putStr "bar". Depending on the order in which those two actions get evaluated, the output is either "foobar", "barfoo" or any state in between. The output is indeterministic as it depends on the specific order of evaluation.
As another example, consider the two values sum [1..10] and 5 * 3. Regardless of the order in which those two get evaluated, they always reduce to the same results. This determinism is something you can usually only guarantee with pure values.
Concurrency and parallelism are two different things.
Concurrency means that you have multiple threads interacting non-deterministically. For example, you might have a chat server where each client is handled by one thread. The non-determinism is essential to the system you're trying to model.
Parallelism is about using multiple threads for simply making your program run faster. However, the end result should be exactly the same as if you run the algorithm sequentially.
Many languages don't have primitives for parallelism, so you have to implement it using concurrency primitives like threads and locks. However, this means that you the programmer have to be careful to ensure that you don't accidentally introduce unwanted non-determinism or other concurrency issues. With explicit parallelism primitives like par and pseq, many of these concerns simply go away.