Files
notes/docs/lectures/osc/05_concurrency2.md
John Gatward 1af40d53d8
Some checks failed
Build and Deploy MkDocs / deploy (push) Failing after 15s
Added osc
2026-03-25 11:15:39 +00:00

5.7 KiB
Raw Blame History

15/10/20

Peterson's Solution

Peterson's solution is a software based solution which worked well on older machines

Two shared variables are used

  1. turn - indicates which process is next to enter its critical section
  2. Boolean flag [2] - indicates that a process is ready to enter its critical section
  • Peterson's solution can be used over multiple processes or threads
  • Peterson's solution for two processes satisfies all critical section requirements (mutual exclusion, progress, fairness)
do {
	flag[i] = true; // i wants to enter critical section
	turn = j; // allow j to access first
	while (flag[j] && turn == j);
	// whilst j wants to access critical section
	// and its js turn, apply busy waiting
	// CRITICAL SECTION
	    counter++
	flag[i] = false;
	// remainder section
} while (...);

Figure: Peterson's solution for process i

do {
	flag[j] = true; // j wants to enter critical section
	turn = i; // allow i to access first
	while (flag[i] && turn == i);
    // whilst i wants to access critical section
    // and its is turn, apply busy waiting
    // CRITICAL SECTION
    	counter++
    flag[j] = false;
    // remainder section
} while (...);

Figure: Peterson's solution for process j

Even when these two processes are interleaved, its unbreakable as there is always a check to see if the other process is in the critical section.

Mutual exclusion requirement:

The variable turn can have at most one value at a time.

  • Both flag[i] and flag[j] are true when they want to enter their critical section
  • Turn is a singular variable that can store only one value
  • Hence while (flag[i] && turn == i); or while (flag[j] && turn == j); is true and at most one process can enter its critical section (mutual exclusion)

Progress: any process must be able to enter its critical section at some point in time

Processes/threads in the remaining code do not influence access to critical sections

If a process j does not want to enter its critical section

  • flag[j] == false
  • white (flag[j] && turn == j) will terminate for process i
  • i enters critical section

Fairness/bounded waiting

Fairly distributed waiting times/process cannot be made to wait indefinitely.

If Pi and Pj both want to enter their critical section

  • flag[i] == flag[j] == true
  • turn is either i or j assuming that turn == i i enters it's critical section
  • i finishes critical section flag[i] = false and then j enters its critical section.

Peterson's solution works when there is two or more processes. Questions on Peterson's solution with more than two solutions is not in the spec.

Disable interrupts whilst executing a critical section and prevent interruptions from I/O devices etc.

For example we see counter ++ as one instruction however it is three instructions in assembly code. If there is an interrupt somewhere in the middle of these three instructions bad things happen.

register = counter;
register = register + 1;
counter = register;

Disabling interrupts may be appropriate on a single CPU machine, not on a multi-core processor though. This means multiple cores can take a value from memory, manipulate that value (in this example iterating it) whilst not knowing the value has already changed on a different core and write back the wrong value to memory. This can lead to 1+1+1=2.

Atomic Instructions

Implement test_and_set() and swap_and_compare() instructions as a set of atomic (uninterruptible) instructions

  • Reading and setting the variables is done as one complete set of instructions
  • If test_and_set() / sawp_and_compare() are called simultaneously they will be executed sequentially.

They are used in combination with global lock variables, assumed to be true (1) is the lock is in use.

Test_and_set()

// Test and set method
boolean test_and_set(boolean * bIsLocked) {
	boolean rv = *bIsLocked;
	*bIsLocked = true;
	return rv;
}

// Example of using test and set method
do {
	// WHILE the lock is in use, apply busy waiting
	while (test_and_set(&bIsLocked));
	// Lock was false, now true
	// CRITICAL SECTION
	...
	bIsLocked = false;
	...
	// remainder section
} while (...)
  • test_and_set() must be atomic.
  • If two processes are using test_and_set() and are interleaved, it can lead to two processes going into the critical section.
// Compare and swap method
int compare_and_swap(int * iIsLocked, int iExpected, int iNewValue) {
	int iTemp = *iIsLocked;
	if(*iIsLocked == iExpected)
		*iIsLocked = iNewValue;
	return iTemp;
}

// Example using compare and swap method
do {
	// While the lock is in use (i.e. == 1), apply busy waiting
	while (compare_and_swap(&iIsLocked, 0, 1));
	// Lock was false, now true
	// CRITICAL SECTION
    ...
    iIsLocked = 0;
    ...
    // remainder section
} while (...);

test_and_set() and swap_and_compare() are hardware instructions and not directly accessible to the user.

Disadvantages:

  • Busy waiting is used. When the process is doing nothing just sitting in a loop, the process is still eating up processor time. If I know the process won't be waiting for long busy waiting is beneficial however if it is a long time a blocking signal will be sent to the process.
  • Deadlock is possible e.g when two locks are requested in opposite orders in different threads.

The OS uses the hardware instructions to implement higher level mechanisms/instructions for mutual exclusion i.e. mutexes and semaphores.