3. Threads and Concurrency Flashcards
What are threads?
Multiple execution processes within a single process.
How are threads different than processes?
A single-threaded process has its own address space and execution context. The OS stores this info in a process control block (PCB).
Threads are part of the same address space but they represent multiple independent execution contexts. Threads have their own private copy of the stack, program counter, registers, etc. The OS stores this info in a more complex process control block (PCB).
Why are threads useful?
- Parallelization
- We can speed up the time to complete work
- Specialization
- We can give higher priority to certain types of tasks
- We can get better performance b/c if a thread is repeatedly executes a smaller portion of the code more of that state will be present in the processor cache (hotter cache)
- More memory efficient than a multi-process implementation b/c threads share an address space. (If we did as a multi-process implementation we’d have to allocate multiple address space + multiple execution context for each process vs just one address space + multiple execution contexts).
- Threads share an address space so the application is more likely to fit into memory and not require as many swaps from disk.
- Lower communication overhead
- Communicating between processes is more costly than communicating between threads.
In summary: More efficient in resource requirements and incur lower overheads for inter-thread communication than inter-process.
Are threads useful on a single CPU?
Or when the number of threads is greater than the number of CPUs?
Yes!
One of the most costly steps during a context switch is the time it takes to create the new virtual to physical address mappings for a new process. But threads don’t pay this cost b/c they share an address space so we don’t have to create new virtual to physical address mappings during a context switch!
So multi-threaing lets us hide more of the latency that’s associated with I/O opperations.
How should threads be represented by an operating system or a system library that provides multi-threading suport?
- Thread data structure (Birell proposed “Thread type”)
- e.g., Thread ID, register values (program counter, stack pointer), stack, attributes
What concurrency and coordination mechanisms (aka synchronization mechanisms) do we need to support threads?
-
Mutual Exclusion: exclusive access to only one thread at a time
- Mutex
-
Waiting on other threads for a specific condition before proceeeding
- Condition variables
- Waking up other threads from a waiting state
What is necessary for thread creation? And what did Birell propose?
- A way to create a thread
- Birell proposed
fork (procedure, args)
- Note: This is NOT a UNIX fork.
- Birell proposed
- A way to determine if a thread is done and retrieve it’s result or at least determine it’s status (e.g., success or failure)
- Birell proposed
join(thread_id)
- When the parent calls join with the id of the child thread it will be blocked until the child completes. Join returns to the parent the result of the child’s computation. It exits, its resources are freed, and its terminated.
- Birell proposed
What’s the difference between mutual exclusion and mutex?
Mutual exclusion is a mechanism.
A mutex is a construct by which mutual exclusion is achieved.
What is a mutex?
A mutex is a construct for mutual exclusion among the execution of concurrent threads.
Use whenever accessing data or state that’s shared among threads.
When a thread locks a mutex it has exclusive access to the shared resource. Other threads will be suspended until the mutex owner releases the lock and they can aquire the lock.
The mutex specification doesn’t make any guarantees about the order of the lock request queue (i.e., requesting first doesn’t guarantee you’ll get it first)
Also, just because there are requests in the queue doesn’t mean the lock will go to one of those. If a thread requests a lock just as its being released it’s possible it could aquire it even though there are pending requests.
What data structure do we need to support a mutex?
- locked status
- owner
- list of blocked threads
What is the critical section?
A critical section is the portion of the code protected by the mutex.
It should be the part of the code that requires that only one thread at a time perform this operation.
Other than the cirtical section, the rest of the code in the program the threads may execute concurrently.
Birel proposed that the critical section is the part between the curly braces of the lock function. In his semantics, calling Lock locks the mutex and then exiting the curly brace unlocks it. In most APIs today you must explicity call Lock and Unlock.
What is a condition variable?
A construct that can be used in conjunction with mutexes to control the behavior concurrent threads.
e.g., It allows us to wait for an event before doing something
What is the condition variable API?
- Condition type
-
Wait(mutex, condition)
- mutex is automatically released and re-aquired on wait
-
Signal(cond)
- notify only one thread waiting on condition
-
Broadcast(cond)
- notify all waiting threads
What data structure is required to support a condition variable?
- mutex reference
- list of waiting threads
What is a spurious wake up?
An unnecessary wake up e.g., waking up a thread when it can’t actually aquire the lock and will have to go immediately back into a waiting state.