Seyed Masoud Hosseini · Overview · Study log · Weekly summaries · Ideas · Search · Transcript · RSS feed
Performance Engineering of Software Systems · Lecture 16 of 23 · 1:22:12
16. Nondeterministic Parallel Programming
Study guide
What this lecture covers
Up to this point the course treated parallel programming as deterministic: Cilk's guarantees mean a race-free program always produces the same result. This lecture crosses into the messier territory of nondeterministic parallel programming, where shared, mutable state is updated by concurrent threads without a guarantee of consistent ordering.
It opens with the "golden rule" (never write nondeterministic parallel code) and the "silver rule" (if you must, have a strategy for managing it), then builds up mutual exclusion with mutexes, the sharp distinction between data races and determinacy races, how mutexes are implemented at the assembly level, deadlock and its three necessary conditions, the classic dining philosophers problem and its lock-ordering fix, and closes with an introduction to lock-based transactional memory as an alternative to hand-placed locks.
Key ideas
- Golden and silver rules: avoid nondeterministic parallel programs when possible; when not possible, always have a strategy (turning off the source of nondeterminism, fixed random seeds, record-replay, encapsulation) for managing and debugging it.
- Atomicity via mutexes: a mutex's lock/unlock calls make a critical section appear, to the rest of the system, as either fully executed or not executed at all, preventing races like a lost-update bug in a hash table insertion.
- Data race vs. determinacy race: a data race is two parallel instructions accessing the same location with no lock held in common and at least one write; a determinacy race is the same access pattern regardless of locking. Programs with locks are typically free of data races but still have determinacy races, and are therefore intentionally nondeterministic.
- No data races does not mean no bugs: a program can lock and unlock around a read and separately around a write, eliminating the data race but destroying atomicity, producing a subtly wrong result.
- Mutex properties: spinning (busy-wait) versus yielding (returns control to the OS); reentrant versus non-reentrant; fair versus unfair. A spinning mutex's implementation checks the lock value before attempting an atomic exchange, because that avoids generating cache-invalidation traffic while waiting.
- Competitive (spin-then-yield) mutex: spin for roughly one context-switch interval, then yield; this guarantees acquiring the lock within about twice the optimal wait time, an instance of the "ski rental problem."
- Deadlock's three conditions: mutual exclusion, non-preemption, and circular waiting; removing any one prevents deadlock. Acquiring locks in a fixed linear order (as in the dining philosophers fix) eliminates circular waiting and thus deadlock.
- Transactional memory: instead of manually placed locks, a critical region is marked as a transaction; the system detects conflicts and aborts/restarts conflicting transactions, useful for algorithms like concurrent graph Gaussian elimination where lock placement is awkward.
Walkthrough
Determinism, and why nondeterminism is dangerous (1:02)
The lecture defines a deterministic program as one where every memory location sees the same sequence of updates on every run, states the golden rule against writing nondeterministic parallel programs (mainly because it breaks debuggability), and the silver rule to always have a test strategy, such as disabling address randomization, fixing random seeds, record-replay, or encapsulating the nondeterminism (as Cilk's randomized scheduler already does).
Mutual exclusion and atomicity (13:15)
Using a hash table with chaining as the running example, the lecture shows how two concurrent insertions without synchronization can corrupt the list (a classic lost-update race), then introduces mutex locks to make the insertion's pointer updates atomic, restoring the invariant that all elements remain reachable in the list.
Data races versus determinacy races (20:23)
The lecture carefully distinguishes a data race (concurrent access to the same location with no common lock, at least one a write) from a determinacy race (same access pattern regardless of locking). Code that uses locks is typically free of data races but still has determinacy races, so it is deliberately nondeterministic; a race detector can still be useful for finding genuine, unintended data races. An example shows that eliminating a data race by locking a read and a write separately does not restore atomicity.
Implementing mutexes (34:42)
Working through x86 assembly for a spinning mutex, the lecture explains why the code first checks the lock's value before attempting the atomic exchange: checking only requires shared cache state, while the exchange requires exclusive/modified state, so checking first avoids generating unnecessary cache-invalidation traffic while spinning. It then contrasts yielding mutexes and introduces the competitive (spin-then-yield) mutex, connecting it to the ski-rental problem and noting a randomized variant achieving a competitive ratio of e/(e-1).
Deadlock and dining philosophers (59:02)
The lecture defines deadlock's three necessary conditions (mutual exclusion, non-preemption, circular waiting), illustrates it with two threads acquiring two locks in opposite orders, and tells the dining philosophers story (with chopsticks), showing that acquiring resources in a fixed linear order prevents the circular wait and proves deadlock cannot occur. It also warns that holding a mutex across a Cilk sync can deadlock a Cilk program even with just one lock, and recommends holding mutexes only within a single strand and for as short a time as possible.
Transactional memory (1:11:10)
Using concurrent graph Gaussian elimination as a motivating example where manual lock placement is awkward, the lecture introduces transactional memory: mark a critical region as a transaction, and let the system detect conflicts and abort/restart as needed. It defines conflict, contention resolution, forward progress, and throughput, then sketches a lock-based transactional-memory algorithm using a finite ownership array (hashing memory locations to a fixed set of fair locks) and a "release, sort, reacquire" protocol that avoids ever needing a single global lock.
Before you watch
- Be comfortable with Cilk's deterministic execution model and the guarantees Cilk provides when programs are free of determinacy races.
- Review the MSI/MESI cache coherence protocol from an earlier lecture, since it explains why spinning mutexes check before exchanging.
- Recall work and span analysis, since the lecture briefly contrasts "easy" deterministic parallel reasoning with the harder nondeterministic case.
Check your understanding
- What is the difference between a data race and a determinacy race, and why can a program have no data races yet still have a bug?
- Why does a well-implemented spinning mutex check the lock's value before attempting an atomic exchange, rather than just attempting the exchange directly?
- What are the three necessary conditions for deadlock, and how does acquiring locks in a fixed linear order prevent it?
- Why can holding a mutex across a Cilk
syncstatement deadlock a program even with a single lock? - In the transactional-memory algorithm described, what problem does the "release, sort, reacquire" protocol solve, and why does it avoid needing a global lock?
Vocabulary
- nondeterministic (adjective)
- Describing a process that can give different results even when run with the same input.
Nondeterministic parallel programs can produce a different answer on each run. - deterministic (adjective)
- Describing a process that always gives the same result for the same input.
A deterministic program produces the same output every time it runs. - mutual exclusion (noun)
- A rule ensuring that only one thread can use a shared resource at a time.
Mutual exclusion stops two threads from changing the same data at once. - mutex (noun)
- A lock used to allow only one thread at a time into a section of code.
The program uses a mutex to protect the shared hash table. - critical section (noun)
- A part of code that must not be run by more than one thread at the same time.
Inserting into the hash table is the critical section protected by the lock. - atomicity (noun)
- The property that an action happens completely or not at all, with no partial state visible to others.
The mutex gives the insertion operation atomicity. - data race (noun)
- A bug where two parallel operations access the same memory with no shared lock and at least one changes it.
A data race can corrupt the linked list during concurrent insertions. - determinacy race (noun)
- A situation where a program's result can differ depending on the order threads run, even without a data race.
Programs using locks are often free of data races but still have a determinacy race. - lost-update bug (noun)
- An error where one thread's change to shared data is silently overwritten by another thread.
Without locking, a lost-update bug can drop one of the inserted elements. - spinning (adjective)
- Describing a wait strategy where a thread repeatedly checks a condition instead of pausing.
A spinning mutex keeps checking the lock instead of giving up the processor. - yielding (adjective)
- Describing a wait strategy where a thread gives control back to the operating system while waiting.
A yielding mutex lets other work run while it waits for the lock. - reentrant (adjective)
- Describing a lock that the same thread can safely acquire again without deadlocking itself.
A reentrant mutex allows a function to lock it twice from the same thread. - cache-invalidation (noun)
- The process of marking cached data as no longer valid because it changed elsewhere.
Checking the lock before exchanging it avoids extra cache-invalidation traffic. - competitive ratio (noun)
- A measure comparing an algorithm's worst-case performance to the best possible performance.
The spin-then-yield mutex has a competitive ratio close to two. - ski rental problem (noun)
- A classic decision problem about choosing between paying repeatedly or paying once upfront, without knowing the future.
The spin-then-yield strategy is inspired by the ski rental problem. - deadlock (noun)
- A situation where two or more threads are stuck waiting for each other forever.
Acquiring locks in different orders can cause a deadlock. - circular waiting (noun)
- A cycle where each thread waits for a resource held by the next thread in the cycle.
Circular waiting is one of the three necessary conditions for deadlock. - non-preemption (noun)
- A condition where a resource cannot be forcibly taken away from the thread holding it.
Non-preemption means the operating system can't just grab the lock back. - dining philosophers (noun)
- A classic example used to illustrate deadlock, involving people sharing a limited set of tools.
The dining philosophers problem shows how fixed ordering avoids deadlock. - transactional memory (noun)
- A system where a block of code is marked as a single unit, and conflicts are detected and retried automatically.
Transactional memory avoids manually placing locks in complicated code. - conflict (noun)
- A situation where two operations try to access the same data in an incompatible way.
The system detects a conflict and restarts one of the transactions. - abort (verb)
- To stop an operation partway through and undo any partial changes.
A conflicting transaction is aborted and tried again later. - throughput (noun)
- The amount of useful work completed in a given amount of time.
Good throughput means many transactions finish successfully per second. - forward progress (noun)
- A guarantee that some thread will eventually complete its work, even under contention.
The system must ensure forward progress so no thread waits forever. - encapsulation (noun)
- Hiding the internal details of a system so its complexity doesn't leak out.
Encapsulation of randomness lets Cilk's scheduler stay easy to test.
Chapters
- 0:00 MIT OpenCourseWare
- 0:22 Nondeterministic Parallel Programming
- 4:31 Golden Rule
- 6:11 Silver Rule
- 10:45 Encapsulation
- 14:26 Mutual Exclusion
- 17:21 Atomicity
- 30:54 Silks
- 35:49 Reentrant Nonreentrant
- 36:53 Fair or Unfair
- 59:27 What is deadlock
From the YouTube description
MIT 6.172 Performance Engineering of Software Systems, Fall 2018
Instructor: Charles Leiserson
View the complete course: https://ocw.mit.edu/6-172F18
YouTube Playlist: https://www.youtube.com/playlist?list=PLUl4u3cNGP63VIBQVWguXxZZi0566y7Wf
Prof. Leiserson discusses nondeterministic parallel programming, atomicity implement through mutexes, determinacy races, and data races. Deadlock and the dining philosopher's problem are also discussed.
License: Creative Commons BY-NC-SA
More information at https://ocw.mit.edu/terms
More courses at https://ocw.mit.edu
← 15. Cache-Oblivious Algorithms · Lecture 17: Synchronization Without Locks →
