https://blog.beautyyu.one//银河美术馆 Gallery of Galaxy2024-03-28T15:12:15.705Zhttps://github.com/jpmonette/feedBeautyyu言醴beautyyuyanli@gmail.comhttps://blog.beautyyu.one/Beautyyu言醴's Bloghttps://blog.beautyyu.one//favicon.svgAll rights reserved 2024, Beautyyu言醴https://blog.beautyyu.one//about2022-04-09T16:00:00.000Z
用 Go 语言的时候,你一看它返回了一个 err,脑子就永远有根弦,要么必须写 if err != nil,要么主动用 _ 忽略掉错误,采用任何一种方式,就算是再粗心的程序员,都清晰地知道自己在做什么,反而更有利于及时的处理错误。 写 Go 的时候感觉自己一直在 if err != nil 正是因为每一个错误都被兜住了,不会漏掉。
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.
1. Threads
1.1 What is a thread?
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.
1.2 Thread switching in an operating system
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.
1.3 Multi-threading and parallelism
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).
2. Processes
2.1 What is a process?
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.
2.2 How does an operating system implement processes?
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.
3. Comparison between Threads and Processes
3.1 Relationship between multi-threading, multiple processes, and parallelism
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.
3.2 Threads are not scaled-down processes
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.
4. Coroutines
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.
虽然它们使用不同的寄存器,但可以共享同一个内存空间,例如用户编写的变量存储在内存的某个位置,现在有两个线程都要读取这个内存空间,所以它们使用的是同一套地址空间。它们可以在两个不同的 CPU 上运行(假设这两个 CPU 都是单线程的原始 CPU。我们知道现代的 CPU 有许多的技术,一个 CPU 其实可以同时运行多个线程)。
二、进程
2.1 进程是什么
以上就是线程的概念。那么进程 (process) 是什么呢?进程是操作系统提供给每个应用程序的一个隔离环境,类似于一个虚拟机,是抽象的一台计算机。为什么说是一个虚拟机?因为它给每个应用程序创建了一种独立使用一台计算机的感觉,而且使用的是一台很朴素的计算机,只有顺序执行的 CPU 和一块独立使用的内存。
一是分配 CPU 的调度机制。调度机制是通过(简单来说)尽量均匀地分配 CPU,使每个应用程序都能运行,就像它拥有独占的 CPU 一样。等到一个进程的使用时间片到期了,硬件就触发一个定时器中断,先保存寄存器的数据,然后把所有计算用的寄存器切换到调度器上,调度器载入下一个进程的寄存器数据,最后把这些寄存器切换到下一个进程。这个过程是程序员不可见的,一个应用程序被“打晕”然后又被唤醒,它看到的寄存器是一样的,是内核给它维护了“这段时间无事发生”的“幻觉”,因此说这个进程看到的是一颗它独占使用、连续使用的 CPU。
近期继续学习操作系统。操作系统改变了我对 Service 这件事物的认知。过去我自然地依表象将操作系统看作一个服务,我提交任务给它去执行,同别的服务一样。然而计算机科学没有魔法,Service 根本上也是和 Hello World 用一样的代码构建的。如何实现通过状态转换实现 Service 的功能?听说南大 OS 是一门谈论状态的课,有机会一定学习一下。
操作系统本身是如何在多个 CPU 上并行的、是如何通过共享的 memory 管理自己的状态、又是如何管理并行 process 的、如何在逻辑上阻塞而物理上不占用资源(trap、sleeplock)、如何不阻塞地处理并发 IO (decoupling)… 操作系统的每个子问题似乎都对应计算机科学中的一大类问题,的确是一个核心的领域。
5月15日,惊闻陈皓先生突发心梗去世。他是一个很棒的前辈,从他的博文中我学到了许多软件工程方面的方法,包括 vim 也是看他的博文入门的。另外也曾在一场 Twitter Space 的交流中听取了他对被动收入的看法:从专业角度出发,把专业做成自己的被动收入。尽管他在推特上有些观点似乎有些保守,但我仍然认为他是一位良师,也曾想过要应聘 MegaEase 的工作。
看到伊洪已经备份了他的推文,也有人准备用他的数据结合 AI 设计“数字永生”。希望他会喜欢这样的“永生”。