- In Windows, each application runs in its own process.
- A process isolates an application from other applications by giving it its own virtual memory and by ensuring that different processes can’t influence each other.
- Each process runs in its own thread.
- A thread is something like a virtualized CPU.
- If an application crashes or hits an infinite loop, only the application’s process is affected.
- Windows must manage all of the threads to ensure they can do their work.
- These management tasks do come with an overhead.
- Each thread is allowed by Windows to execute for a certain time period.
- After this period ends, the thread is paused and Windows switches to another thread.
- This is called context switching.
- In practice, this means that Windows has to do some work to make it happen.
- The current thread is using a certain area of memory and uses CPU registers and other state data.
- Windows has to make sure that the whole context of the thread is saved and restored on each switch.
- In general, a thread is a sequence of instructions that may run concurrently with other instruction sequences.
- A program that enables more than one sequence to execute concurrently is multi-threaded.
Time Slicing
- An operating system simulates multiple threads running concurrently via a mechanism known as time slicing.
- Even with multiple processors, there is generally a demand for more threads than there are processors, and as a result, time slicing occurs.
- Time slicing is a mechanism whereby the operating system switches execution from one thread (sequence of instructions) to the next.
- This process happens so quickly that it appears the threads are executing simultaneously.
- The period of time that the processor executes a particular thread before switching to another is the time slice or quantum.
- Since a thread is often waiting for various events (E.g. an I/O operation), switching to different thread results in more efficient execution.
- The reason is the processor is not idly waiting for the operation to complete.
- However, switching from one thread to the next does create some overhead.
- If there are too many threads, the switching overhead begins to noticeably affect performance, and adding additional threads will likely decrease performance further.
- The processor spends time switching from one thread to another instead of accomplishing the work of each thread.
Complexity of Multi-Threading
- Considerable complexity exists in multi-threaded programs to maintain atomicity, avoids deadlocks, and avoid introduce execution uncertainty such as race conditions.
- Multi-threaded programming includes the following complexities:
- Monitoring an asynchronous operation state for completion:
- This includes determining when an asynchronous operation has completed, preferably not by polling the thread’s state or by blocking and waiting.
- Thread pooling: This avoids the significant cost of starting and tearing down threads.
- In addition, thread pooling avoids the creation of too many threads, such that the system spends more time switching threads than running them.
- Avoiding deadlocks: This involves preventing the occurrence of deadlocks while attempting to protect the data from simultaneous access by two different threads.
- Providing atomicity across operations and synchronizing data access:
- Adding synchronization around groups of operations ensures that operations execute as a single unit and that they are appropriately interrupted by another thread.
- Locking is provided so that two different threads do not access the data simultaneously.
- Furthermore, anytime a method is long-running, it is probable that multithreaded programming is going to be required invoking the long-running method asynchronously.
Concurrency vs Asynchrony