Lecture 004 - Concurrency

Classical Concurrency

Goal for multiple node concurrency is to mitigate one node's failure

Critical Section: piece of code accessing a shared resource, usually variables or data structures

Race Condition: Multiple threads of execution enter CS at the same time, update shared resource, leading to undesirable outcome

Indeterminate Program: One or more Race Conditions, output of program depending on ordering, non deterministic

Mutual Exclusion: guarantee that only a single thread/process enters a CS, avoiding races

Mutual Exclusion: Atomic setting and testing implemented by hardware.

Semaphores: shared address space with integer variable with increase and decrease operations.

Note that binary semaphores is logically identical with mutex.

Spin Lock: wast resource and unclear if Insert(x) will make progress. Potentially a livelock.

Spin Lock: wast resource and unclear if Insert(x) will make progress. Potentially a livelock.

Condition Variables (cvars): thread suspended until activated by other threads (more efficient than spin lock)

cvar.Wait():
  Must be called after locking mutex.
  Atomically: release mutex & suspend operation
  When resume, lock mutex (but maybe not right away)
cvar.Signal():
  If no thread suspended, then NO-OP
  Wake up (at least) one suspended thread.
  (Typically do within scope of mutex, but not required)

CVAR Example: klzzwxh:0003 will first unlock mutex and wait for other to process first until Signal() is called. (Note that there is a typo, klzzwxh:0004 should be klzzwxh:0005)

CVAR Example: cvar.wait() will first unlock mutex and wait for other to process first until Signal() is called. (Note that there is a typo, b.sb.Signal() should be b.cvar.Signal())

Resume isn't safe since it may not lock mutex right away... so you end up with a while loop to recheck condition.

Mesa semantics (looser) vs Hoare Semantics (tighter)

Concurrency Model in Golang

There are channels and goroutines.

The idea is instead of communicating with shared memory, we simulate shared memory by communication.

When channel capacity is 0, it becomes a synchronization point. You can only send to a chanel of capacity 0 if and only if there exist processes currently listening.

Note that if you are running in one core, 0 capacity channel might have logical deadlock, but I believe golang will work it out.

Note that there isn't really reason to use channel size other than 0 or 1. Big channel size might hide concurrency bugs.

You can use channels to implement mutex.

Limitation to channels:

Q: How do you change a lightbulb in concurrent programming?

A. You take the lamp to a secure area so nobody else can try to change the lightbulb while you're changing it. Alternatively, you might get a lamp with lightbulbs that can't be changed, and just get a new lamp when the lightbulb goes out.

Table of Content