The concurrency arc closes as the others did β with the professor, the student, and an aching head.
Professor:So, does your head hurt now?
Student:(taking two Motrin tablets) Well, some. Itβs hard to think about all the ways threads can interleave.
Professor:Indeed it is. Iβm always amazed that when concurrent execution is involved, just a few lines of code can become nearly impossible to understand.
Student:Itβs kind of embarrassing, as a Computer Scientist, not to be able to make sense of five lines of code.
Professor:Oh, donβt feel too badly. If you look through the first papers on concurrent algorithms, they are sometimes wrong! And the authors often professors!
Student:(gasps) Professors can be⦠umm⦠wrong?
Professor:Yes, itβs true. Though donβt tell anybody β itβs one of our trade secrets. But if concurrent code is so hard to get right, how are we supposed to write it correctly? Thatβs the real question. So β whatβs in your mental model now?
The studentβs answer, itemized (click each row β it notes where the belief was earned):
| the belief | |
|---|---|
| 1 | Threads share everything β and that's the problem |
| 2 | A lock is built, not given |
| 3 | Add locks carefully β one big lock first |
| 4 | Condition variables let a thread sleep until state changes |
| 5 | One primitive can be both lock and CV |
| 6 | Concurrency bugs come in patterns |
| 7 | Threads aren't the only way |
| 8 | The best concurrency is the concurrency you avoid |
Professor:A fine model! Now let me give you the three rules I live by for writing correct concurrent code.
The Professorβs Three Rules of Concurrency
1. Keep it simple. Avoid complex thread interactions; use well-known, tried-and-true paradigms β simple locking lock A variable holding one of two states β available (free) or acquired (held by exactly one thread, the owner) β with lock() not returning until the caller holds it, and unlock() freeing it. Programmers bracket critical sections with them, making the section execute as if atomic. defined in ch. 28 β open in glossary , a producer/consumer producer/consumer problem Dijkstra's canonical synchronization problem (a.k.a. the bounded-buffer problem): one or more producer threads place items into a shared bounded buffer while one or more consumers remove them. Correct solutions need mutual exclusion on the buffer plus making producers wait when it is full and consumers wait when it is empty β a lock plus two condition variables (empty and fill). defined in ch. 30 β open in glossary queue.2. Use concurrency only when absolutely needed. Avoid it if you can β there is nothing worse than premature optimization.
3. If you truly need parallelism, seek simplified forms. MapReduce lets you write parallel data analysis with no locks, condition variables, or other nasty things at all.
Student:Like simple locking, and maybe a producer-consumer queue? Andβ¦ why add threads if you donβt need them?
Professor:Exactly! Those are common paradigms you can now build. And for real parallelism, look at MapReduce β parallelism without any of the horrific complexities deadlock A situation where a set of threads are each holding a resource and waiting for one held by another, forming a cycle in which none can proceed β e.g. every dining philosopher holding their left fork and waiting for their right, or a consumer holding a mutex while blocked on 'full' while the producer that would post 'full' is blocked on that mutex. Studied in depth in ch32.
defined in ch. 31 β open in glossary
weβve talked about.
Student:Map-Reduce, huh? Sounds interesting β Iβll have to read more about it on my own.
Professor:Good! You should. What we learn together is only the barest introduction. Read, read, and read some more! Then write code, and write some more. As Gladwell says in Outliers, you need roughly 10,000 hours to become a real expert β you canβt do that inside class time. (Want a fun place to practice deadlock? Try the gamified Deadlock Empire.)
Student:Wow, Iβm not sure if thatβs depressing or uplifting. But Iβll assume the latter, and get to work! Time to write some more concurrent codeβ¦
Next: Part III β Persistence
We virtualized one CPU into many, one memory into many address spaces, and made many threads cooperate without stepping on each other. The final piece: making data survive β I/O devices, spinning disks and SSDs, RAID, file systems, journaling, and the long fight to keep bits safe across crashes and time. The third easy piece begins.Check yourself: the concurrency wrap-up
1.What is the Professor's FIRST rule for writing correct concurrent code?
2.What is the Professor's second rule?
3.The Professor's third rule points to MapReduce as an example of what?
4.Recapping the Part: what fundamentally causes a race condition (chapter 26)?
5.Recap: why does the producer/consumer solution need TWO condition variables (chapter 30)?
5 questions