Processes and threads: the core model of multitasking in modern operating systems
Have you ever encountered such a scenario? While writing reports in Word, I was browsing GitHub and Spotify with Chrome hanging on, playing songs, and Thunder was still downloading the material library in the background. Obviously the CPU only has a few cores, but the operating system can make these programs appear to be running at the same time?
Behind this are today's protagonists - process and thread, and the "multitasking" technology stack that supports them.
1. Overview of multitasking: from "serial" to "parallel + concurrency"
1.1 What is multitasking?
Simply put, multitasking allows the operating system to schedule multiple programs/services to run at the same time (or seemingly at the same time), maximizing the use of hardware resources and improving user experience and system throughput.
Common multitasking needs:
- Write code at the front desk / browse Taobao, and scan with anti-virus software in the background
- Open multiple tabs at the same time: documents, videos, reference materials
- The backend server responds to hundreds or thousands of HTTP requests simultaneously
1.2 The underlying differences from single-core to multi-core
The term "running simultaneously" has completely different implementation mechanisms on single-core CPUs and multi-core CPUs:
"Pseudo-parallelism·True concurrency" in the single-core era
Early CPUs had only one physical core and could not really do two things at the same time. The operating system relies on time slice rotation scheduling to create the illusion of "parallelism":
- Allocate a small period of execution time (time slice, usually 10-20 milliseconds) to each active task
- The CPU continuously context switches between tasks: saving the registers, stacks, etc. status of the current task, and loading the status of the next task.
- The switching speed is extremely fast (nanosecond operation), far beyond human perception, so we feel that all programs are working simultaneously
"True parallelism + true concurrency" in the multi-core/hyper-threading era
Modern CPUs are generally 2 cores and 4 threads, 4 cores and 8 threads or even more:
- Each physical core can perform tasks independently, which is "true parallelism"
- Hyper-Threading Technology (Intel Hyper-Threading / AMD Simultaneous Multi-Threading) allows one physical core to run two lightweight threads at the same time by reusing idle hardware resources within the core.
- The scheduler of the operating system will allocate different tasks to different cores/hardware threads, combined with time slice rotation, to further improve concurrency capabilities
2. Core model: process and thread
The core abstractions of the current multi-tasking system are process and thread - each has its own division of labor, and no one can replace the other.
2.1 Process: The basic unit of resource allocation
You can think of a process as an independently running program instance, which comes with its own "house", "furniture" and "water and electricity":
- Independent memory space: code segments, data segments, heaps, stacks, file descriptors and other resources, completely isolated between processes (the crash of one process will not directly bring down other processes)
- Contains at least 1 main thread: The process itself is just a "resource container", and the threads inside it are what really do the work.
- Inter-process communication (IPC) is complex: Isolation brings security, but it also requires special mechanisms (pipes, shared memory, message queues, Sockets, etc.) to transfer data between processes.
- Creation and switching are expensive: need to allocate memory, establish process control block, switch page table, etc.
For example: When you open "Chrome.exe", each tab and extension may be an independent process (Chrome's multi-process architecture).
2.2 Thread: The basic unit of CPU scheduling
Threads are "workers" in the process, sharing the "house" and "furniture" of the process, and only carrying their own "toolbox":
- Shared process memory space: Multiple threads can directly read and write the same global variables and heap memory, and the communication efficiency is extremely high
- Only retain independent execution context: program counter, registers, stack pointer, local stack, etc. The creation and switching overhead is only a few tenths of that of the process
- Synchronization mechanism required: Although shared memory is fast, it is prone to "resource competition" (for example, two threads modify a variable at the same time, resulting in data confusion), and must be protected by locks, semaphores, etc.
- The crash of one thread may affect the entire process: because everyone lives in the same resource pool (such as memory out of bounds, illegal instructions)
Also using Chrome as an example: inside a tab process, there will be rendering threads, JS threads, network threads, IO threads and other workers who work together to divide the work.
3. Common multi-task programming models
Depending on the characteristics of the task (CPU intensive or IO intensive), we can choose different models.
3.1 Multi-process model
Applicable scenarios: CPU-intensive tasks (video transcoding, machine learning training, large-scale numerical calculations), scenarios requiring extremely high stability (browser tabs, game servers)
advantage:
- Complete isolation between processes, a crash will not affect others
- Can fully utilize the true parallel capabilities of multi-core CPUs (without restrictions such as Python GIL)
shortcoming:
- High creation and context switching costs
- Inter-process communication (IPC) is troublesome and not as efficient as thread shared memory
3.2 Multi-threading model
Applicable scenarios: IO-intensive tasks (network requests, file reading and writing, database queries) - such tasks spend most of the time "waiting", and the CPU is idle when it is idle. It is better to let other threads do the work.
advantage:
- Extremely low creation and switching costs
- Shared memory, simple and efficient communication
shortcoming:
- Requires a complex synchronization mechanism (using too many locks can lead to deadlock and livelock)
- A thread crash can bring down the entire process
- GIL (Global Interpreter Lock) exists in languages such as Python/CRuby: only one thread can execute bytecode at the same time, so Python multi-threading is not effective for CPU-intensive tasks and can only improve IO-intensive concurrency efficiency
Tips: If your Python program needs to perform CPU-intensive calculations, remember to replace multi-threading with multi-process, or use C extensions to bypass the GIL.
3.3 Mixed model: process + thread / coroutine
Many complex applications now use this idea:
- Use multi-process first to ensure stability and multi-core utilization
- Each process uses multi-threads/coroutines to handle IO-intensive concurrent requests
For example, Node.js's Cluster mode and Python's Gunicorn + Gevent mode all fall into this category.
4. Python practice: minimalist examples of three models
Python has very rich built-in multi-tasking support. The following minimalist code is used to demonstrate multi-process, multi-thread and coroutine respectively.
4.1 Multi-process example (multiprocessing)
Note: In Windows environment, the startup code of multiple processes must be placed in
if __name__ == "__main__":block, otherwise child processes will be created infinitely!
4.2 Multi-threading Example (threading)
Multi-threading is best suited for handling IO-intensive tasks, such as requesting 3 websites at the same time:
4.3 Coroutine example (asyncio)
Coroutines are lightweight threads in user mode. The switching is completely controlled by the program itself. The overhead is smaller than that of threads. It is very suitable for very large-scale IO concurrency (such as processing thousands of HTTP requests at the same time).
5. Pitfall avoidance guide for multi-tasking programming
Multitasking code is prone to bugs, and such bugs are often difficult to reproduce (called "race conditions"). Here are some common considerations:
-
Sync problem When multiple threads/coroutines access shared resources, locks must be used (
threading.Lock/asyncio.Lock), semaphore and other mechanism protection. -
Deadlock Avoidance Deadlock means that two threads are stuck forever waiting for each other to release the lock. Solution:
- Acquire locks in a fixed order
- Set lock timeout
-
Impact of GIL Python multi-threading is not effective for CPU-intensive tasks, please switch to multi-process.
-
Debugging Difficulty
- Try to use logs instead
print, to facilitate subsequent troubleshooting - Use specialized multitasking debugging tools (e.g.
pdbmulti-threaded mode,tracemallocTracking memory leaks)
- Thread-safe data structures
Python built-in
list、dictNot thread safe. It is recommended to use in multi-threaded environment:queue.Queue(Thread-safe queue)multiprocessing.Manager().dict()(Process Security Dictionary)
6. Summary
Modern multi-tasking technology has developed from "single-core time slice rotation" to "multi-core hyper-threading parallelism" to "user mode coroutine", "containerized isolation" and "distributed computing", and the solutions are becoming more and more abundant.
As a developer, just remember one core principle: Choose a model based on the type of task:
- CPU intensive: multiple processes
- IO intensive: multi-threading/coroutine
- High concurrency + high stability: multi-process + multi-thread/coroutine
Although Python is restricted by GIL, its multi-tasking ecosystem (multiprocessing、threading、asyncio、aiohttp、gevent) is very mature and can handle most scenarios.
*As long as the model is selected correctly, the CPU will not take advantage of it, and your program can take off "at the same time". *

