• Traffic example
• Two cars come to an intersection on opposing sides
• They both want to turn left
• They pull into the intersection and try to turn left…
• …but each blocks the other.
• Circular wait
• two (or more…) threads are both waiting on resources the other has.
• Mutual exclusion
• only one thread can use the resource at once.
• Hold-and-wait
• a thread doesn’t give up any resources it already holds while attempting to acquire a new one.
• it’s “greedy.”
• No preemption of the resource
• no one can forcibly take the resource from the thread that has locked it.
• If you make any one of those conditions false, a deadlock will end.
• If the two threads don’t share a resource, they can’t deadlock (duh).
• If the two threads can both share the resource, they can’t deadlock.
• If the two threads let go of one resource before trying to grab another, they can’t deadlock.
• If the resource is forcibly taken from one of the threads, the deadlock ends.
• Unfortunately…
• It’s one of those NP-complete problems.
• But you can avoid deadlocks
• This isn’t a cure-all, but it eliminates a lot of issues.
2. Acquire resources in the same order in all threads.
• That way, either a thread gets them all or it doesn’t get any of them.
• Maybe use a different synchronization primitive, or make the critical section bigger (not the greatest, but sometimes necessary).
4. Use pthread_cond_timedwait when possible
• This waits until either the condition is signaled or a certain amount of time passes
• This way, even “sleepy” threads occasionally wake up and can avoid/eliminate a deadlock.

## But what if threads aren’t enough?

• Threads are great and all, but…
• They do have a big weakness: security
• Consider a web browser.
• Let’s say you make one thread to run the code for each tab.
• Let’s say one tab has your bank account logged in, and another has a malicious script running.
• Oops.
• Sandboxing is a security technique where a process’s permissions (abilities) are limited.
• For example, you can disable its access to most files; limit its RAM and CPU usage; disable network access etc.
• This is a pretty powerful technique, but it can only be applied to processes, not threads
• (in general)
• This is very important for running untrusted code - such as random scripts downloaded from shady websites
• Another problem with multithreading is…
• If one thread crashes, often it takes the whole process down with it.
• This is not necessarily the case, but it often is.
• Even if your program has mitigations to recover from a failure, it might still be in an inconsistent (unknown) state.
• Having multiple processes avoids this – if one process crashes, oh well, the others are unaffected.

## Interprocess Communication (IPC)

• Let’s go back to our browser example.
• Chrome and Firefox both use multiple processes as a way of sandboxing tabs (open sites).
• But they look like a single “program”. You can switch tabs, copy and paste things between them, click on a link and it opens in a new tab…
• This is because the multiple processes are able to communicate.
• But, like… the whole point of processes is to isolate them…
• Ah, whatever! Let’s allow them to communicate through safe means.
• Files
• Probably the simplest method
• The filesystem is shared state among all the processes!
• Have one process make a file…
• …and another wait for that file to show up.
• It’s not very fast, though.
• Have to get the hard drive involved…
• Might not really need the file for more than the duration of the program…
• But everything is a file!
• So why not just… make a kind of file that connects two processes?
• Pipes
• Remember these? ps | less
• This hooks the stdout of ps to the stdin of less
• This is handled by the filesystem part of the OS
• You could even hook them up in a mutual fashion, or make your own pipes with… you guessed it, pipe()
• Each “direction” is a character device
• However, it’s possible to run into deadlocks with this, if you’re not careful…
• buffering might cause both processes to wait forever on each other
• P1 writes into its own buffer; P2 writes into its own buffer; then both processes try to read from the other, but since the data was buffered, both will be waiting for data that will never arrive!
• Can avoid this by disabling buffering on one or both directions
• In addition, this requires context switching every time one process sends data to the other, which might introduce a big bottleneck if they need very high-speed communication
• Shared Memory
• Virtual memory is a hell of a thing
• We can ask the OS to let two processes share one piece of memory (like, a page)
• It’s like setting up a little hole in the barrier between the processes
• Like one of those secure counters at a bank or convenience store or ticket counter or whatever
• Now both processes can read/write this memory, and the other process will see it
• You can use regular old threading synchronization (mutexes!) in this shared window
• However…
• This is a complicated and advanced feature
• Not all kernels will support it, or have the same level of support for it
• It also requires some hardware support (to maintain cache coherence)
• Sockets
• One final way is to “abuse” the networking capabilities of the system (if it has them)
• The way network connections work is by using a socket, which is an abstract concept like a file
• It’s basically a pipe, but connected over a network.
• It can also have all sorts of other capabilities, like encryption, compression, guarantees of delivery etc.
• You can have multiple processes on the same system communicate through sockets
• They just connect to “localhost” - the current computer
• IP address 127.0.0.1
• But even better, you can have them connect over a network too!
• So you can have two processes on different computers communicating with each other
• And your program doesn’t have to know the difference!