Concurrent Programming

Memory Sharing between threads

Something is rotten in the state of Denmark

boolean a = false, b = false;
int x = -1, y = -1;

void test(int execution) {
    var t1 = new Thread(() -> {
        sleep(1);
        a = true;
        y = b ? 0 : 1;
    });
    var t2 = new Thread(() -> {
        sleep(1);
        b = true;
        x = a ? 0 : 1;
    });
    startAll(t1, t2);
    joinAll(t1, t2);

    if (x == 1 && y == 1)
        throw new RuntimeException("Failed at execution number : " + execution);
}

Alternatives

  • First Alternative: t1 runs first
    • t1 writes true to a then reads b and sees false –> writes 1 to y
    • t2 writes true to b then reads a and sees true –> writes 0 to x
    • x == 0 && y == 1
  • Second Alternative: t2 runs first.
    • x == 1 && y = 0
  • Third Alternative: Partial run
    • t1 writes true to a and is interrupted.
    • t2 writes true to b
    • t1 and t2 both see true so they write 0 to x and y
    • x == 0 && y == 0
  • Conclusion: there is no execution in which x = 1 and y = 1

Let's try it!

public static void main(String[] args) {
	for (var i = 0; i < 10_000; i++)
    	new Test().test(i);
}
Exception in thread "main" java.lang.RuntimeException: Failed at execution number : 880
	at org.example.counter.Test.test(Test.java:25)
	at org.example.counter.Test.main(Test.java:30)

Why ?

  • Threads can run in different cores using there own memory (For example L1 cache)
  • Threads are synchronized trough shared memory. For example after they are joined.
  • Then this is possible:
    • t1 writes true to a then reads b and sees false –> writes 1 to y
    • t2 writes true to b then reads a and sees false -> writes 1 to x
  • Because t2 have their own value of a that has not yet been synchronized

A posible solution in Java

Declare a and b volatile

volatile boolean a, b;

This forces the JVM to synchronize the variable to and from shared memory

Volatile is lightweight and faster than locking

Critical Session Implementations

  • Disable Interruptions
  • Busy Loop
  • Special Machine Instructions
    • testAndSet
    • compareAndSwap

Disable Interruptions

public class Counter {
    int value = 0;

    void increment() {
        disableInterruptions();
        
        int localCounter = value;
        localCounter = localCounter + 1;
        value = localCounter;

        enableInterruptions();
    }
}
  • Only work in uni-processors
  • Usually inefficient

Basic implementation


boolean lock = false;

void increment() {
	while (lock) {
		// Busy Loop
	}
	lock = true;
	// Begin Critical Section
		int localCounter = value;
		System.out.println(threadName() + " reads counter as: " + localCounter);
	
		localCounter = localCounter + 1;
		value = localCounter;
	// End Critical Section
	lock = false;
	System.out.println(threadName() + " updated counter to: " + value);
}

Needs Volatile!

volatile boolean lock = false; 

To ensure that when one threads modify lock the others see it

But It has a problem

while (lock) {
	// Busy Loop
}
// I can have a Context switch here !
lock = true;

There is Hardware Support to do this right

For example, Compare and Set instruction

void increment() {
	while (v.compareAndSet(false, true)) { 
		// busyLoop
	}
    
	// Critical section
	// ...
}

Atomically test and modify the value on sucess

Hardware support

  • The good:
    • Applicable to any number of processes.
  • The bad:
    • Busy-waiting consumes processor time.
  • Solution:
    • After some spinning return control to the operating system.
    • while (lock.compareAndSet(false, true)) Thread.yield();

Disclaimer

This is to show How it can be implemented

You should use the libraries provided by the language/framework

What if I want to do it (the bad way) in Rust???

fn counter() -> i32 {
    let mut counter = 0;
    thread::scope(|s| {
        s.spawn(|| counter += 1);
        s.spawn(|| counter += 1);
    });
    return counter
}
error[E0499]: cannot borrow `counter` as mutable more than once at a time

... Etc..

The borrow checker protects me from data races!