3.1 KiB
3.1 KiB
7. Concurrency
Processes
Processes and ids
- To define concurrent process, wrap
beginblocks inprocess <name> = <id>andend process. - Processes cannot share label names
- Processes can share state
- They usually need some synchronization. Consider this model:
---- MODULE core7_1_reader_writer ----
EXTENDS Integers, Sequences, TLC
(* --algorithm reader_writer
variables
queue = <<>>;
total = 0;
process writer = 1
begin
AddToQueue:
queue := Append(queue, 1);
end process;
process reader = 0
begin
ReadFromQueue:
total := total + Head(queue);
queue := Tail(queue);
end process;
end algorithm; *)
====
When running the model, it will fail trying to read from an empty queue. Some approaches we could have:
- skip dequeuing if queue is empty
- block reader until queue is not empty
- return a default value
- report an error of our own, instead of having TLC flag it
Invariants can use the process id applied to pc, like pc[0] for the reader PC
in that example.
Process sets
Process values do not have to be plain values, like naturals or strings, so we can write
---- MODULE foo ----
Writers == {1, 2, 3}
(*----algoorithm foo
process writer \in Writers
\* ...
end process
end algorithm*)
=====
Using a goto <first label in process> is equivalent to placing the whole process in a while TRUE loop.
self
In a process, self is the value of the current process (its id).
selfis available in macrosselfis NOT available in processes defined with a constant value insteadprocess name \in set.
await
- A process containing
awaitstatements will only run if ALL the statements in the CURRENT label evaluate to TRUE - await interacts a little oddly with variable updates
- it will be based on the updated value if directly embedded
- but not if the variable is used via a defined operator.
- do not uses updates variables in
awaitstatements
- if no label in a process can run, possibly because they contain at least one
awaitevaluating to FALSE, the spec deadlocks.
Example 7.7 will then deadlock, because the reader will never be able to read.
We can make this succeed by disabling DEADLOCK_CHECK in the .CFG.
Procedures
- Procedures are like macros, but can contain labels
- Defined in
Sequences - Procedure MUST include a
returnstatement - The
returnstatement MUST be reached - unlike macros, they are not called just by name by using
call Name(val1, ...); callstatements MUST be followed by- either a
goto - or a label
- or another return, if the call was from within a
procedure
- either a
Finding more invariants
- Never forget the type invariant, like in the 7.11 threads example:
---- MODULE foo ----
\* Must be AFTER the PlusCal translation.
TypeInvariant ==
/\ counter \in 0..NumThreads
/\ tmp \in [Threads -> 0..NumThreads] \* tmp is a function by process value
/\ lock \in Threads \union {NULL}
====
- In the threads example, any process (thread) value is
>= counter. Two ways to write it:
CounterNotLtTmp1 ==
tmp \in [Threads -> 0..counter]
CounterNotLtTmp2 ==
\A t \in Threads:
tmp[t] <= counter