Skip to content

Commit

Permalink
update execution flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Chang-LeHung committed Oct 20, 2023
1 parent aa6aacd commit 5b7119b
Showing 1 changed file with 13 additions and 7 deletions.
20 changes: 13 additions & 7 deletions pvm/15executionflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

进程是一个非常古老的概念,根据 wiki 的描述,进程是一个正在执行的计算机程序,这里说的计算机程序是指的是能够直接被操作系统加载执行的程序,比如你通过编译器编译之后的 c/c++ 程序。

举个例子,你在 shell 当中敲出的 ./a.out 在按下回车之后,a.out 就会被执行起来,这个被操作系统执行的程序就是一个进程。在一个进程内部会有很多的资源,比如打开的文件,申请的内存,接收到的信号等等,这些信息都是由内核来维护。关于进程有一个非常重要的概念,就是进程的内存地址空间,一个进程当中主要有代码、数据、堆和执行栈:
举个例子,你在 shell 当中敲出的 ` ./a.out ` 在按下回车之后,`a.out` 就会被执行起来,这个被操作系统执行的程序就是一个进程。在一个进程内部会有很多的资源,比如打开的文件,申请的内存,接收到的信号等等,这些信息都是由内核来维护。关于进程有一个非常重要的概念,就是进程的内存地址空间,一个进程当中主要有代码、数据、堆和执行栈:

![92mml](../images/92mml.png)

这里我们不过多的去分析这一点,现在就需要知道在一个进程当中主要有这 4 个东西,而且在内核当中会有数据结构去保存他们。程序被操作系统加载之后可以被操作系统放到 CPU 上运行。我们可以同时启动多个进程,让操作系统去调度,而且随着体系结构的发展,现在的机器上都是多核机器,同时启动多个进程可以让他们同时执行。

在具体的编程时我们会有一个需求,我们希望并行的去执行程序,而且他们可以修改共有的内存,当一个进程修改之后能够被另外一个进程看到,从这个角度来说他们就需要有同一个地址空间,这样就可以实现这一点了,而且这种方式有一个好处就是节省内存资源,比如只需要保存一份内存的地址空间了。
在编程时我们会有一个需求,我们希望并行的去执行程序,而且他们可以修改共有的内存,当一个进程修改之后能够被另外一个进程看到,从这个角度来说他们就需要有同一个地址空间,这样就可以实现这一点了,而且这种方式有一个好处就是节省内存资源,比如只需要保存一份内存的地址空间了。

上面谈到的实现进程的方式实际上被称作轻量级进程,也被叫做线程。具体来说就是可以在一个进程内部启动多个线程,这些线程之前有这相同的内存地址空间,这些线程能够同时被操作系统调度到不同的核心上同时执行。我们现在在 linux 上使用的线程是NPTL (Native POSIX Threads Library),从 glibc2.3.2 开始支持,而且要求 linux 2.6 之后的特性。在前面的内容我们谈到了,在同一个进程内部的线程是可以共享一些进程拥有的数据的,比如:

Expand All @@ -24,6 +24,12 @@
- 当前工作目录。
- 虚拟地址空间。

线程也有自己的私有数据,比如:

- 程序执行栈空间。
- 寄存器状态。
- 线程的线程号。

在 linux 当中创建线程和进程的系统调用分别为 `clone``fork`,如果为了创建线程的话我们可以不使用这么低层级的 API,我们可以通过 NPTL 提供的 `pthread_create` 方法创建线程执行相应的方法。

```c
Expand Down Expand Up @@ -85,9 +91,9 @@ if __name__ == '__main__':
t.join()
```

现在有一个问题是,在 Python 当中真的是使用 pthread_create 来创建线程的吗?Python 当中的线程和我们常说的线程是一致的吗?
现在有一个问题是,在 Python 当中真的是使用 pthread_create 来创建线程的吗(在 Linux 当中)?Python 当中的线程和我们常说的线程是一致的吗?

我们现在来分析一下 threading 的源代码,线程的 start (也就是 Thread 类的方法)方法如下:
我们现在来分析一下 threading 的源代码,线程的 start (也就是 Thread 类的 start 方法)方法如下:

```python
def start(self):
Expand All @@ -109,7 +115,7 @@ if __name__ == '__main__':

```

在上面的代码当中最核心的一行代码就是 `_start_new_thread(self._bootstrap, ())`,这行代码的含义是启动一个新的线程去执行 `self._bootstrap` ,在 `self._bootstrap` 当中会调用 `_bootstrap_inner`,在 `_bootstrap_inner` 当中会调用 Thread 的 run 方法,而在这个方法当中最终调用了我们传递给 Thread 类的函数。
在上面的代码当中最核心的一行代码就是 `_start_new_thread(self._bootstrap, ())`,这行代码的含义是启动一个新的线程去执行 `self._bootstrap` ,在 `self._bootstrap` 当中会调用 `_bootstrap_inner`,在 `_bootstrap_inner` 当中会调用 Thread 的 run 方法,而在`run`方法当中最终调用了我们传递给 Thread 类的函数。

```python
def run(self):
Expand Down Expand Up @@ -242,7 +248,7 @@ t_bootstrap(void *boot_raw)
}
```
从上面的整个创建线程的流程来看,当我们在 Python 层面创建一个线程之后,最终会调用 `pthread_create` 函数,真正创建一个线程(我们在前面已经讨论过这种线程能够被操作系统调度在 CPU 上运行,如果是多核机器的话,这两个线程可以在同一个时刻运行)去执行相应的 Python 代码。
从上面的整个创建线程的流程来看,当我们在 Python 层面创建一个线程之后,最终会调用 `pthread_create` 函数,真正创建一个线程(我们在前面已经讨论过这种线程能够被操作系统调度在 CPU 上运行,如果是多核机器的话,这两个线程可以在同一个时刻运行)去执行相应的 Python 代码,也就是说当我们使用 threading 模块创建一个线程的时候,最终确实使用了 `pthread_create` 创建了一个线程
## 协程
Expand Down Expand Up @@ -280,7 +286,7 @@ def event_loop():

## 总结

在本篇文章当中主要讨论了进程、线程和协程的区别,以及在 Linux 当中创建线程的 API,以及 CPython 当中创建线程的流程,最后讨论了一下协程的使用场景,为什么需要使用协程以及在 Python 当中是如何使用协程的。只有当你的程序是有比较多的 IO 操作的时候,你才需要考虑使用协程,因为协程提升的是 CPU 的利用率,如果你的程序本来 CPU 利用率就很高了,比如有很多的数学计算,你就不需要使用协程了。
在本篇文章当中主要讨论了进程、线程和协程的区别,以及在 Linux 当中创建线程的 API,以及 CPython 当中创建线程的流程,最后讨论了一下协程的使用场景,为什么需要使用协程以及在 Python 当中是如何使用协程的。只有当你的程序是有比较多的 IO 操作的时候,你才需要考虑使用协程,因为协程提升的是 CPU 的利用率,如果你的程序本来 CPU 利用率就很高了,比如有很多的数学计算,你就不需要使用协程了,这样做就可以避免额外的切换开销了

---

Expand Down

0 comments on commit 5b7119b

Please sign in to comment.