Let's start with the terms used throughout these notes. Some sound similar but describe different things: how tasks are ordered, whether they run at the same time, and what happens while they wait. These ideas apply across programming languages; later notes explain how individual languages implement them.
Tasks can run one after another, or their execution can overlap. Which approach is possible depends partly on whether one task needs another task's result.
In sequential execution, each task finishes before the next begins. For example, a program might read a file, process its contents, and then save the result. Each step has the previous step's output available when it starts.
Characteristics of Sequential Execution:
Non-sequential execution allows some tasks to overlap or run in a different order. Independent tasks offer the most freedom, but any dependencies still have to be respected. A program can process two files independently, for example, but must wait for both results before combining them.
Characteristics of Non-sequential Execution:
Concurrency and parallelism both involve multiple tasks. The distinction is whether those tasks make progress over overlapping periods or actually execute at the same instant.
Concurrency means handling multiple tasks whose execution overlaps in time. On a single CPU core, a program can switch between tasks, making progress on each without executing them simultaneously.
Characteristics of Concurrency:
Parallelism means executing multiple tasks at the same instant, for example on different CPU cores. A large task can be split into smaller pieces that run in parallel, provided their dependencies allow it.
Characteristics of Parallelism:
Synchronous and asynchronous execution describe how a caller waits for an operation to finish. This is a different question from whether several tasks execute in parallel.
With a synchronous call, the caller waits for the operation to finish before continuing. This makes the flow easy to follow, but a slow operation can hold up that caller. Other threads in the program may still run during the wait.
Characteristics of Synchronous Execution:
An asynchronous operation lets the caller start work and handle its completion later. While the operation is pending, other work can proceed. This is useful for disk or network operations, where much of the time may be spent waiting. It does not, by itself, mean that code runs on another CPU core.
Characteristics of Asynchronous Execution:
These descriptions can apply together. A program can use synchronous calls in several parallel threads, or handle asynchronous operations concurrently on a single thread.
| Characteristic | Sequential | Non-Sequential | Concurrent | Parallel | Asynchronous | Synchronous |
| Definition | Tasks are executed one after another in a specific order. | Tasks may overlap or change order where dependencies allow. | Multiple tasks make progress within overlapping time periods. | Multiple tasks are executed simultaneously on multiple processors. | Tasks are initiated and proceed independently of other tasks, often with callbacks or promises to handle completion. | The caller waits for an operation to complete before continuing. |
| Order of Execution | Strict and predictable order. | Flexible order. | Overlapping time periods, but not necessarily simultaneous. | Simultaneously on different processors. | Initiated independently, can complete at any time. | Calls complete before the caller continues; other threads may still run. |
| Task Scheduling | Simple and straightforward. | Flexible, can change dynamically. | Managed by the scheduler to ensure progress of all tasks. | Managed by the system, often requires multiple processors. | Can be scheduled to run at any time, often managed by event-driven programming models. | The calling thread waits; other threads may be scheduled. |
| Complexity | Low complexity, easy to design and debug. | More complex due to flexibility. | Moderate complexity, requires careful management of resources. | High complexity, requires handling of synchronization and resource sharing. | Moderate to high complexity, requires handling of callbacks, promises, or other mechanisms to manage independent tasks. | Low complexity, but can lead to inefficiencies if tasks have to wait for long periods. |
| Performance | Can be slower due to waiting for each task to complete before starting the next. | Can be efficient if tasks are independent and do not need a specific order. | Can improve performance by overlapping tasks, but may involve context switching overhead. | Can significantly improve performance for CPU-bound tasks by using multiple processors. | Can improve responsiveness and resource utilization, especially in I/O-bound tasks. | Can lead to inefficiencies if tasks involve a lot of waiting, as each task has to wait for the previous one to complete. |
| Use Cases | Simple scripts, batch processing where order is crucial. | Event-driven systems, where tasks can be handled as they come. | Multithreading applications, GUI applications where multiple tasks need to be handled concurrently. | High-performance computing, data processing tasks that can be divided into smaller, independent tasks. | Web servers handling multiple requests, applications with I/O operations where waiting for responses would be inefficient. | Systems where order of operations is crucial and tasks are dependent on the completion of previous tasks, such as transaction systems. |
A process is a running instance of a program, with its own memory and resources managed by the operating system. A text editor might run in one process, while a browser may use several to handle tabs and background work. The operating system also runs processes for services such as scheduled jobs and network communication.
The operating system manages the resources processes need: CPU time, memory, and access to devices. It also keeps track of processes as they start, wait for work, and finish.
Responsibilities of the OS in Process Management:
The OS maintains records of active processes, often described collectively as the process table. These records include each process's PID, state, scheduling information, and memory allocation. Together with saved thread state, they let the OS pause execution and resume it later.
A process can be in one of several states during its lifecycle. The three primary states are:
A thread is a path of execution within a process. A process has one or more threads, each with its own program counter, registers, and call stack. Threads in the same process share its address space and resources such as open files. They are sometimes called lightweight processes because creating a thread usually requires fewer resources than creating a separate process.
For example, a text editor might use one thread to handle keyboard input and another to check spelling. This can keep typing responsive while the spelling check runs. Threads may also run in parallel, but whether they improve performance depends on the work, the runtime, and the available hardware.
For threads managed by the OS, the scheduler decides which ones get CPU time. Language runtimes can also manage their own tasks on top of these threads.
A program's memory allocation is often divided into four segments:
The main distinction is how resources are owned and shared. Processes normally have separate address spaces, making them useful when tasks need isolation.
Characteristics of Processes:
Threads belong to a process and share its address space. This makes exchanging data convenient, but also means that one thread can change data another thread is using. Access to shared mutable data needs coordination.
Characteristics of Threads:
Differences Between Processes and Threads:
| Aspect | Process | Thread |
| Independence | Processes are independent instances that run in separate address spaces. | Threads are subsets of a process and run within the same address space as the process. |
| Memory | Processes have separate address spaces, meaning each process has its own memory area. | Threads share the address space of their parent process, allowing them to access the same memory and data. |
| Communication | Processes require Inter-Process Communication (IPC) mechanisms like pipes, message queues, or shared memory to communicate with each other. | Threads can communicate directly by accessing shared memory within the process. |
| State Information | Processes carry considerable state information, including process ID, process state, memory information, and more. | Threads maintain minimal state information, typically just a thread ID, program counter, register set, and stack. |
| Resource Sharing | Processes own separate resources but can explicitly share some, such as shared memory. | Threads share resources such as code, data, and open files with other threads within the same process. |
| Creation Overhead | Process creation has higher overhead because it involves allocating a separate memory space and other resources. | Thread creation has lower overhead since threads share resources and memory with the parent process. |
| Context Switching | Context switching between processes involves more overhead due to switching separate memory spaces and state information. | Context switching between threads is faster and involves less overhead because they share the same memory space and resources. |
Here's a table comparing common life cycle stages of threads and processes. The exact names vary by OS or runtime; suspended states appear in some models that include moving processes out of main memory.
| Life Cycle Stage | Thread Life Cycle | Process Life Cycle |
| New | Thread is in the process of being created. | Process is in the process of being created. |
| Ready | Thread is ready to run when CPU is available. | Process is ready to execute when CPU is available. |
| Running | Thread is actively executing instructions. | Process is actively executing instructions. |
| Waiting/Blocked | Thread is waiting for resources or I/O. | Process is waiting for resources or I/O. |
| Timed Waiting | Thread is waiting for a specified time. | (Not typically a separate state, but can be considered under waiting) |
| Terminated | Thread has completed execution or is aborted. | Process has completed execution or is terminated. |
| Ready Suspended | (Not typically a separate state, but can be inferred from 'Ready' and 'Blocked') | Process is in secondary storage and ready to execute when moved to main memory. |
| Blocked Suspended | (Not typically a separate state, but can be inferred from 'Blocked') | Process is in secondary storage and waiting for an event before moving to main memory. |
Practical Implications:
Before trying to speed up a program, find out where it spends its time. Is it mostly computing, or waiting for input/output (I/O), such as disk reads and network responses? The answer helps determine whether parallel computation or overlapping waits is likely to help. A program can also move between these bottlenecks during different stages of its work.
A task is CPU-bound when computation is the main limit on its speed. For example, calculating a large set of prime numbers may keep a CPU core busy with very little I/O. A faster algorithm or parallel execution across cores may reduce the running time.
Characteristics of CPU-Bound Tasks:
Strategies for Optimizing CPU-Bound Tasks:
Example of a CPU-Bound Scenario:
I/O waiting
CPU Processing ----Task 1---->----Task 2---->
In this example, the CPU is continuously processing tasks with minimal waiting for I/O operations. The speed at which tasks are completed is primarily limited by the CPU's processing power.
A task is I/O-bound when input or output is the main limit on its speed. Downloading files, querying a remote database, and reading from disk can involve substantial waiting. Overlapping independent requests can help, although the disk, network, or remote service still limits how much work can be completed.
Characteristics of I/O-Bound Tasks:
Strategies for Optimizing I/O-Bound Tasks:
Example of an I/O-Bound Scenario:
I/O waiting -----request-----> ------request------> ------request------>
CPU Processing ---> ----> ---->
In this example, the CPU is often waiting for I/O operations to complete before it can continue processing. The speed at which tasks are completed is primarily limited by the efficiency of the I/O operations.