RECALL: PROCESS def: a *process* (or task) in an OS is a program that is being run Characteristics: - is active (running or waiting to run) - has its own memory PAS = Process Address Space - has various permissions WRITING A SERVER: USE CASE FOR PARALLELISM Want a server that can: - handle multiple asynchronous requests from many clients - access shared data - reserve virtual resources for clients e.g., airline seats How to do that? One way: a process for each request they access a database (files) better way: requests share memory inside computer execute in parallel share some memory HOW TO SUPPORT PARALLELISM? Modern computers have many Cores + GPUs... How to write programs that use them? - Several processes, each uses a separate core, but: Goals: - parallel execution - share memory - low overheads for: creation switching cooperation THREADS def: a *thread* is a unit of parallel execution in a process Characteristics: - share address space with other threads (in the same process) - each has its own stack and local storage - each can execute on its own core CONCURRENCY Apparent concurrency: - threads that execute by interleaving instructions (sharing a core, timesharing simulating parallelism) True concurrency - threads that execute simultaneously multiple instructions at the same time (multiple cores, parallelism) RACE CONDITIONS Imagine 2 threads, T1 and T2 that share a global int variable k and both execute: k = k + 1; How is this compiled? load k lit 1 add sto What does the hardware do? sequence accesses to where k is stored POSSIBLE EXECUTIONS global k: 0 T1 T2 k_1: 0 k_2: 0 o_1: 1 o_2: 1 r_1: 1 r_2: 1 (writes r_1) (waiting...) global k: 1 (writes r_2) global k: 1 T1 T2 k_1: 0 (waiting ...) o_1: 1 r_1: 1 (writes r_1) global k: 1 k_2: 1 o_2: 1 r_2: 2 (writes r_2) global k: 2 POSSIBLE EXECUTIONS Suppose int a[2] is a global, initially a[0] == 0 and a[1] == 0 and T1 and T2 both execute: a[0] = a[0]+4; a[1] = a[1]+3; T1 T2 a0_1: 0 a0_2: 0 f_1: 4 f_2: 4 r0_1: 4 r0_2: 4 (writes r0_1) (waiting...) a[0]: 4 a[1]: 0 (writes r0_2) a[0]: 4 a[1]: 0 a1_1: 0 a1_2: 0 t_1: 3 t_2: 3 r1_1: 3 r1_2: 3 (writes r1_1) (waiting...) a[0]: 4 a[1]: 3 (writes r1_2) a[0]: 4 a[1]: 3 T1 T2 a0_1: 0 (waiting ...) f_1: 4 r0_1: 4 (writes r0_1) a[0]: 4 a[1]: 0 a0_2: 4 f_2: 4 r0_2: 8 (writes r0_2) a[0]: 8 a[1]: 0 ... RACE CONDITIONS def: a *race condition* occurs when two or more threads can update the state of shared resources in ways that may produce different final states def: a *critical section* is an area of code in which an update to a shared resource occurs that can result in different final states def: *mutual exclusion* is a technique that prohibits more than one thread from being in a critical section at the same time GOALS OF SYNCHRONIZATION MECHANISMS def: a *serial execution* is an execution equivalent to executing each thread by itself (one at a time) def: an execution is *serializable* if it is equivalent to a serial execution def: an execution is *atomic* iff it always finishes completely or not at all Goals: Allow programmers to: - serialize threads - while having system run efficiently as much in parallel as possible SAFE SYNCHRONIZATION MECHANISMS Locking: a locked resource can only be used by the thread that locked it lock(); // critical section code unlock(); Monitors: an abstract server that only handles one thread at a time typically controls some resource found in CSP, Erlang LOW-LEVEL IMPLEMENTATION How to implement locks? In hardware: atomic test-and-set or compare-and-swap instructions In an OS disable interrupts to do atomic actions