The fifth week of CST 334 started a study of threads and synchronization mechanisms.
A thread can be thought of as a mini process – there are many similarities between the two. Ultimately, a thread is a separate line of execution within a process. All threads in a process inhabit the same address space and share the same code and data. Though, importantly, each thread has its own stack. This is due to the independent execution capability of threads – in order to execute independently, each thread must have its own stack to facilitate local variable storage and procedure calls. Similar to processes, the OS must save and restore the execution state of a thread during a context switch to another process / thread. Notably, a context switch between threads in the same process is cheaper than a context switch between processes, as since threads in a process share the same address space, the OS does not need to alter the memory map in any way.
Multiple threads in a single process share memory. While this makes interthread communication easy, there are some major caveats that must be understood. Most importantly, there is no guarantee where in execution a thread will be preempted and stop running. If multiple threads are accessing a shared resource, great care must be taken that the shared resource is not accidentally left in an in-between state due to a context switch. For example, consider a simple line of code: x = x + 1. There are three operations taking place at the instruction level: loading x from memory, incrementing x, and storing x back into memory. If the thread doing this is preempted right after it loads x, and another thread then subsequently changes x before the interrupted thread is resumed, the value set by the intermediate thread will be lost. Situations such as this are called race conditions, where the timing of two or more threads when accessing a shared resource causes indeterminant program state.
The main way to combat race conditions, and other threading issues, is to employ synchronization mechanisms – the most common of which is the lock. A lock is used to prevent multiple threads from accessing the same shared resource simultaneously. If a thread is preempted while it holds a lock to a shared resource, no other thread should be able to access that resource. Locks usually require a mix of hardware and OS support to be implemented. There are other synchronization mechanisms of course, though they are beyond the scope of this blog post.
No comments:
Post a Comment