Concurrent Programming

Mutual exclusion implications

The main issue

  • Non-determinism caused by concurrent threads accessing shared mutable state.
  • It helps to encapsulate state in actors or transactions, but the fundamental problem stays the same.

Non-determinism = parallel processing + mutable state.

  • To get deterministic processing, avoid the mutable state!
  • Avoiding mutable state means programming functionally.

Race Condition

  • A race condition occurs when two or more operations are executed concurrently and their result does not correspond to the expected result of a serialized execution (logical sequence) of those operations.

Example in Java

public class Counter {
    int value = 0;

    void increment() {
        int localCounter = value;
        System.out.println(threadName() + " reads counter as: " + localCounter);

        localCounter = localCounter + 1;
        
        value = localCounter;
        System.out.println(threadName() + " updated counter to: " + value);
    }
}

Example in Java

public class Main {
    public static void main(String[] args) {
        var counter = new Counter();

        // Two threads trying to increment the counter simultaneously
        Thread t1 = new Thread(counter::increment, "Thread 1");
        Thread t2 = new Thread(counter::increment, "Thread 2");

        startAll(t1, t2);
        joinAll(t1, t2);

        System.out.println("Expected value: 2, actual value: " + counter.value);
    }
}

Run the example

Thread 1 reads counter as: 0
Thread 2 reads counter as: 0
Thread 1 updated counter to: 1
Thread 2 updated counter to: 1
Expected value: 2, actual value: 1

Data Race Diagram

Solution: Enforce Single Access

If we enforce a rule that only one process may enter the method increment at a time then:

  • Thread1 enters increment first and creates a lock
  • Thread2 tries to enter but is blocked
  • Thread1 completes execution and releases the lock
  • Thread2 resumes and executes increment
public class Counter {
    int value = 0;

    synchronized void increment() {
        int localCounter = value;
        System.out.println(threadName() + " reads counter as: " + localCounter);

        localCounter = localCounter + 1;
        
        value = localCounter;
        System.out.println(threadName() + " updated counter to: " + value);
    }
}

Competition among Processes for Resources

One need and two main problems:

  • Need for Mutual Exclusion. Only on process at a time be allowed in the critical section.
  • Deadlock.
  • Starvation.

Deadlock

  • Deadlock can be defined as the permanent blocking of a set of processes that compete for resources.
  • A set of processes is deadlocked when each process in the set is blocked waiting for a resource that is hold by another blocked process in the set.

Deadlock

image

Deadlock Diagram

image

Example in Java

public void transferTo(BankAccountSync to, double amount) {
  synchronized (this) {
	  // Lock over 'this' acquired
	
	  // Try to lock 'to'
	  synchronized (to) {
		// Lock over 't0' acquired.
		withdraw(amount);
		to.deposit(amount);
	  }
  	  // lock over 'to' released
  }
  // lock over 'this' released
}

Example in Java

var t1 = new Thread(() ->  a.transferTo(b, 200));
var t2 = new Thread(() ->  b.transferTo(a, 100));

Output

Thread-0 Waiting to lock 'A'.
Thread-0 Lock over 'A' acquired.
Thread-1 Waiting to lock 'B'.
Thread-1 Lock over 'B' acquired.
Thread-0 Waiting to lock 'B'.
Thread-1 Waiting to lock 'A'.

Starvation

  • A situation where a process is perpetually denied the resources it needs to progress:
    • while other processes are favored.
    • leading to indefinite waiting.

Starvation