Re: Thread and Process
Jul 28, 2023
This is a perennial topic, but I hope to explain it in a clearer manner.
Hello everyone, in this article I would like to talk about the difference between process and thread.
This is a commonly discussed topic and perhaps every programmer has been asked about it during interviews or exams. However, when I first learned about these concepts and searched for information online, I found that most explanations were not detailed enough or didn't cover the key points. So I would like to share my understanding on this topic and hopefully express it clearly.
Let's start with threads. What are threads? As the name suggests, a thread is like a line, referring to a sequence of program instructions executed in order. For example, if we have a+b, it has a linear logic flow: first input a, then input b, add a and b, and finally output the result. This sequential execution is also apparent in assembly language, representing what we call a "line" of execution.
A thread refers to a linearly executing program, which is a logical definition of a thread.
From the perspective of the CPU or the operating system, the operating system is a program running on the CPU. Assuming we have a simple CPU that works by fetching instructions, decoding them, executing them, reading and writing to memory, and writing back to registers in a sequence, it executes one instruction at a time in the order specified by the programmer.
When the operating system starts, it has a main thread, known as the kernel thread, which goes through initialization and enters the scheduler loop. It is important to note that the process of starting and initializing the kernel thread until it enters the scheduler is linear, just like a regular program we write, one line after another. We can refer to this as the kernel thread. Once inside the scheduler loop, a second thread appears, which are the threads of user processes. Here, when I say "process", it may lead to some ambiguity, but I mean the threads of user-written application programs.
The user-written application program, such as a+b, is compiled into a series of instructions that represent a continuous logical flow independent of the kernel's logic. So user threads and kernel threads are two different logical entities, or two different threads.
During the scheduler loop, the kernel thread switches to a user thread. During this thread switch, the system saves the register state of the kernel thread and switches to the logic of the user thread. This thread switch enables the scheduler to run user-written threads, switching between the linear logic of the kernel and the linear logic of the user program. This is a functionality of the operating system kernel.
From the perspective of the CPU, it actually executes sequentially, but it first executes the kernel thread and then the kernel switches its thread to a user thread, achieving thread switching. Therefore, from the perspective of the CPU and the operating system, threads are defined in this way.
From a top-level perspective, from the viewpoint of writing multi-threaded programs, the purpose of having multiple threads is generally for parallel computing. The operating system can assign these threads to multiple CPUs to run simultaneously. For example, if I have two single-threaded CPUs, the operating system can assign each thread to one CPU for execution.
Although they use different sets of registers, they can share the same memory space. For example, if a variable written by the user is stored in a specific memory location, and now two threads need to read from this memory space, they are using the same address space. They can run on two different CPUs (assuming these two CPUs are single-threaded primitive CPUs; we know that modern CPUs have many technologies, and one CPU can actually run multiple threads simultaneously).
That was about threads. Now what about processes? A process is an isolated environment provided by the operating system to each application program, similar to a "virtual machine." Why do we call it a virtual machine? Because it creates an illusion that each application program has exclusive access to a computer, and it uses a simple, linear computer with only a sequentially executing CPU and a dedicated memory.
The essence of a process is to provide an isolated environment for each application program, like having its own dedicated computer. A process has at least one thread, right? It has at least one segment of continuous logic, which may be implemented with multiple threads but at least one thread.
A piece of code, according to the concept of Turing machines, is stored in a specific location in memory. Let's not think about the physical aspect of memory, let's focus on the virtual memory provided by a process for an application program.
This memory is quite simple. It starts with a fixed address containing a segment of continuous code. Below that, there is a heap starting from another fixed address, and above that, there is a stack starting from a different address. This concept is known as the heap and stack. A process has this memory space. As a programmer, whether writing in a high-level or assembly language, we can consider ourselves writing code on this virtual machine and running our code on it. So when we discuss the stack and heap in memory, we are referring to the stack and heap on this virtual space.
This is what a process is. How does it simulate the exclusive use of CPU and memory? This is one of the fundamental functions of the operating system.
First, we have the scheduling mechanism for CPU allocation. The scheduling mechanism aims to distribute CPU time as evenly as possible, allowing each application program to run as if it has exclusive access to the CPU. When the time slice for a process expires, the hardware triggers a timer interrupt. It saves the register data, switches all calculation-related registers to the scheduler, loads the register data of the next process, and finally switches these registers to the next process. This process is invisible to the programmer. An application program is "stunned" and then awakened, perceiving that the registers are the same, creating the "illusion" that "nothing happened during this time." Thus, this process perceives that it has the exclusive and continuous use of the CPU.
Secondly, we have the virtual memory mechanism for memory allocation. Virtual memory primarily operates through page table mapping. By following certain rules, such as dividing physical memory into segments and extracting a few segments, these segments are combined to form contiguous memory, creating virtual memory for process usage. There is an address mapping relationship between this physical memory and virtual memory, maintained by the kernel.
Therefore, a process is like a computer with its own dedicated processor and memory, allowing an application program or a programmer to consider themselves as writing and running code on a standalone computer. This is what a process is, essentially a simulation or abstraction of a computer.
Multi-processing refers to the simultaneous execution of multiple applications. Whether it is multi-threading or multi-processing, they can run in parallel on a modern computer. However, it doesn't mean that they always run in parallel.
For example, if I only have a single-threaded CPU, no matter how many threads I write, they cannot be executed simultaneously on that CPU; they have to take turns using the CPU. In this case, this is essentially coroutines, mentioned in the following section.
Alternatively, even if I have multiple physically excellent CPUs, but my operating system scheduler decides to allocate all CPU resources to one process at a time, in this situation, multiple processes cannot run in parallel.
Although it may sound strange, theoretically it is possible. Parallelism and multi-threading/multi-processing are not inherently interconnected.
In general, the purpose of multithreading is to achieve parallel computing, while the purpose of multiprocessing is not necessarily parallelism. The essence of multiprocessing is to provide isolated environments for multiple applications. Therefore, when multiple threads are used within a process, they utilize the same set of contexts, while multiple processes use different contexts and memory spaces. They are fundamentally different: with different meanings, goals, and implementation methods.
We often hear statements like "a process contains multiple threads." This statement may lead to the misconception that a thread is a lower-level version of a process, a tiny process. However, this feeling is incorrect because threads and processes are two different concepts. Threads do not derive from processes. As mentioned earlier, they are two different dimensions of concepts.
Finally, there is something called coroutines. Conceptually, coroutines are indeed linear logic, as described at the beginning of this article.
However, unlike multithreading, coroutines do not support parallelism. Why not parallel? Because parallelism may lead to data race issues.
So, multiple coroutines refer to running multiple logical threads on a single thread, just like thread switching on a single-threaded CPU as mentioned earlier. It is physically sequential and does not involve parallel execution, but logically it has multiple threads.
This is my understanding of the differences between multi-processing, multithreading, and coroutines. Thank you for reading.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.