Suppose there is some resource that we all want to be able to access, but we can’t use at the same time.
Example: Memory. I could have multiple processes on my laptop, that could have shared memory between multiple threads. What happens if two threads write to the same memory address at the same time? I have no idea, but probably something bad
Race Condition: The situation where you don’t know who’s going to get to the shared resource first.
You probably want to have some standard algorithm or protocol to negotiate the interactions with the shared resource to make sure everything goes smoothly.
//in critical section (currently accessing the shared resource we care about)
bool in_cs[2];
// active [2] means skip the init part, and just create two of these proctypes
active [2] proctype a_process() {
restart:
atomic {
//_pid is a variable that every process has-- process id
in_cs[_pid] = 1;
in_cs[_pid] = 0;
}
goto restart;
}
We kind of cheat here by wrapping the code block in atomic
(which makes sure the whole block is executed at once, so there can be no overlap in the critical section. How can we replicate this without using atomic?
assert(!in_cs[1-_pid])
But this will fail! Because there’s nothing stopping the other process from entering the critical section.
We need to add a flag
to indicate each process’s intention to talk
bool in_cs[2];
bool flag[2];
active [2] proctype a_process() {
restart:
flag[1-_pid] == 0;
flag[_pid] = 1;
in_cs[_pid] = 1;
assert(!in_cs[1-_pid]);
in_cs[_pid] = 0;
flag[_pid] = 0;
goto restart;
}
flag[1-_pid] == 0;
will cause the process to block until the expression is true. As a side effect, this opens the possibility of deadlock
When we run it: uh oh! Our assertion is violated.
Here’s an example:
Tim sees that the student’s hand is not raised, so he decides he is going to raise his hand. Before Tim actually has the chance to raise his hand, the student sees that Tim’s hand is not raised and raises his own hand. Tim then proceeds to raise his hand (having previously checked that the student’s hand was not raised), and both enter the critical section.
How can we fix this?
Note: You can generate an image of the finite state machine using a sequence of commands (that we will provide to you with this week’s assignment)
So now we have mutual exclusion, which is great! We also have deadlock, which is not so great.
bool in_cs[2];
byte victim = 255;
active [2] proctype a_process() {
restart:
victim = _pid; // I will wait
victim != _pid; // Wait until you are no longer the person waiting
in_cs[_pid] = 1;
assert(!in_cs[1-_pid]);
in_cs[_pid] = 0;
goto restart;
}
We’ve over-abstracted the model. We accidentally made it so that both processes always want to go into the critical section.
bool in_cs[2];
byte victim = 255;
active [2] proctype a_process() {
restart:
do
:: goto restart;
:: victim = _pid; // I will wait
victim != _pid; // Wait until you are no longer the person waiting
in_cs[_pid] = 1;
assert(!in_cs[1-_pid]);
in_cs[_pid] = 0;
}
Spin doesn’t catch the deadlock here. Why?
Because one process can always move forward-- the process that doesn’t want the critical section can always enter the do loop, goto restart, and repeat.
ltl strong_no_deadlocks {
always (victim == 0 -> eventually (in_cs[0] || in_cs[1]))
}
This also doesn’t work! Unfortunately, these are both broken locking algorithms. What we need is a combination of flags and deferring.