Let's Talk Concurrency with Sir Tony Hoare
erlang-solutions.com
erlang-solutions.com
Only possible drawback is that they might perform worse than the alternative, at least in some cases.
Seems very closely related to condition variables - except you specify the condition at the lock site rather than via a “trigger” before an unlock. I think Google’s internal Mutex class had a LockWhen(callback) which is a pleasure to work with. Definitely an awesome concept.
To understand it better here's the solution to the producer/consumer problem (from wikipedia), using mutexes and semaphores:
mtx buffer_mutex;
sem fillCount = 0;
sem emptyCount = BUFFER_SIZE;
procedure producer()
{
while (true)
{
item = produceItem();
down(emptyCount);
down(buffer_mutex);
putItemIntoBuffer(item);
up(buffer_mutex);
up(fillCount);
}
}
procedure consumer()
{
while (true)
{
down(fillCount);
down(buffer_mutex);
item = removeItemFromBuffer();
up(buffer_mutex);
up(emptyCount);
consumeItem(item);
}
}
and the same problem solved using CCRs: int avl = 0;
procedure producer()
{
while (true)
{
item = produceItem();
region R when (avl < BUFFER_SIZE)
{
putItemIntoBuffer(item);
avl++;
}
}
}
procedure consumer()
{
while (true)
{
region R when (avl > 0)
{
item = removeItemFromBuffer();
avl--;
}
consumeItem(item);
}
}
The most important thing about CCRs is that they are supposed to guarantee fairness, along with mutual exclusion. A well-designed solution uses two binary semaphore queues to achieve this.If anyone wants to see what an implementation looks like here's a small C library I wrote a while ago, to use for some of my own personal projects, and to solve some homework for Uni https://github.com/Gikoskos/libccr
Yes, entry barriers: http://www.ada-auth.org/standards/12rm/html/RM-9-5-2.html#S0...
The consumer/producer routines (entries) conceptually belong to the object/resource that is operated on (protected object) but are executed by the calling task.
Below is an Actor implementation of a read priority solution to the readers writers problem:
ReadPriority[aDatabase:*ReadersWriter*]:*ReadersWriterManager*
// Invariant: **Nonempty** #writing# ⇨ **IsEmpty** #reading#
**Locals**(Queue(#writersQ#, #readersQ#),
Crowd(#reading#),
AtMostOne(#writing#)),
**Handlers**(
⟦scheduler⟧ ↦ **As** myScheduler, // myScheduler facet of this manager
upgrade[newVersion] ↦
(**CancellAll** #readersQ# **and** #writersQ# **and** #reading# **and** #writing#
**for** **Become** newVersion)
myScheduler **implements** *ReadersWriter* **Handlers**(
read[aQuery] ↦
**Enqueue** #readersQ# **when** **Nonempty** #writing# **or** #writersQ# **or** #readersQ#
**for** aDatabase.read[aQuery] **thru** #reading#
**permit** #readersQ#
**afterward** **Permit** #writersQ# **when** **IsEmpty** #reading#
**else** #readersQ# **when** **IsEmpty** #writersQ#,
write[anUpdate] ↦
**Enqueue** #writersQ# **when** **Nonempty** #reading# **or** #readersQ# **or** #writing# **or** #writersQ#
**for** aDatabase.write[anUpdate] **thru** #writing#
**afterward** **Permit** #readersQ# **else** #writersQ#)▮If I might try to elucidate what Butcher explained, the Actor model used by Erlang/Elixir was supposed to be a "purer" object-oriented programming model that defined how objects passed information between each other rather than the state and attributes of an object, and it worked well concurrently because objects don't share state outside of messages. However, actors are expensive because they tightly couple an object (the transport object) with a process (the mode of communication) unnecessarily.
CSP improves on this model by decoupling objects and processes. It eliminates the need for a process and defines a "routine" instead as a state machine for an imperative code segment, and in doing so creates a one-to-many relationship between a given routine and the hardware-defined threadpool. Buffer objects on a given routine and only exlicitly block when you need to. When an imperative segment of code is blocked in a routine, the routine relinquishes control of the underlying thread while managing state, and when it is unblocked, can begin executing on perhaps another thread with the given state. So it's easier to make imperative code concurrent. State machines are also much cheaper to create than an actual process. CSP is highly effective running concurrent tasks that may have lots of blocking events, such as tasks issued through network, which may be why a lot of the newer network-centric libraries like Kubernetes are written in a CSP-first language like Golang.
I'd definitely buy the book, I've learned a lot (never understood why Golang was so popular until now): https://pragprog.com/book/pb7con/seven-concurrency-models-in...
Actors are always cheaper and faster than CSP processes with channels or "transport objects" as you call them. In a way actors can be described as cancellable asynchronously programmable channels in CSP. Asynchrony is key for implementations to achieve high performance and cancellation together with asynchrony are keys to model unbounded nondeterminism of the real world. Synchronous semantics greatly limit achievable performance and lack of cancellation limits usefulness. Hence no language exists where CSP actually works acceptably well (even Go struggles with it since the beginning, but they still refuse to admit that it was an awful choice).
In fact, if a channel is desired it can be efficiently implemented as an Actor with put and get messages.
Could anyone comment on what that framework is in current terms? Googling 'simulated time train' doesn't really help. Thanks!
The simulation module was a framework for defining parallel processes that worked with discrete, scheduled events, a bit like actors. This was intended to model real-world problems such as traffic systems or factories, that complex interactions and dependencies. A simulation had a time axis, so it ran the processes in simulated time, instead of real time. This may be what Hoare was referring to.
Further reading:
http://simula67.at.ifi.uio.no/Archive/Intro-simula/intro.pdf
http://heim.ifi.uio.no/~steinkr/papers/HiNC1-webversion-simu...
Simula-67 (Dahl and Nyaaard) used co-routines in which there was no parallel execution.
You might find Actors more intuitive. For example, see the following video by Hewitt, Meijer and Szyperski: The Actor Model (everything you wanted to know, but were afraid to ask)
https://channel9.msdn.com/Shows/Going+Deep/Hewitt-Meijer-and...
The principle of Actor induction is:
1. Suppose that an Actor x has property P when it is created.
2. Further suppose that if x has property P when it receives a communication,
then it has property P when it has processed the communication.
3. Then x always has the property P.[1] Unless you have cool initials.