读阮一峰dalao的文章—如何理解进程和线程?

看到了阮一峰dalao的一篇文章,聊聊我的看法。

参考:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

 

一、我对阮一峰dalao的文章总结

(1)cpu

计算机的核心是cpu,它承担了所有的计算任务。就像一座工厂一样,时刻在运行。

(2)单次运行任务

工厂的资源是有限的,比如说电力,一次只能供给一个车间使用。也就是说,同一时刻只能有一间车间开工,别的车间必须停工。含义就是,单个cpu一次只能运行一个任务(一个进程)。

(3)进程

进程就好比工厂中的车间,它代表cpu所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

(4)线程

一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。

(5)进程的内存空间(我的个人理解)

原则上来说,每个车间是独立的,车间中的空间是不能共享的。这就说明,进程的内存空间彼此是相互独立的,不能共享(存在例外情况,进程间的通信需要使用共享的内存空间)。

(6)线程的内存空间

车间内的空间是工人们共享的。车间里存在着许多房间,是每个工人都可以使用的。这象征着一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

(7)独占内存空间

那么问题来了,房间有大有小,有些房间可以容纳多人,有些房间最多只能容纳一个人。比如说厕所,里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。

(8)锁

一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫”互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。

(9)信号量

有些房间可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。

这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做”信号量”(Semaphore),用来保证多个线程不会互相冲突。

不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。

(10)操作系统的设计

为了满足以上的情况,操作系统的设计必须具备以下的简单功能:

  1. 以多进程形式,允许多个任务同时运行;
  2. 以多线程形式,允许单个任务分成不同的部分运行;
  3. 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

二、更多的讨论

(1)文章存在的问题(网友herodot提出)

1.没有体现出进程、线程的特点

车间/电力/人三者,来比喻进程/CPU/线程三者,其实既没有体现出进程作为程序的一次执行(有独立的内存空间)这一特点,也没有体现出线程作为CPU调度单位这一特点。

人和电力的关系是什么呢?所以“车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的”,这句话就很难理解。

那么问题来了,cpu和线程之间的关系是什么?

个人认为,进程根据先来先、短作业优先、优先级、时间轮片法等算法,抢占cpu资源。进程获得cpu资源后(不仅是cpu,进程负责申请、抢占所有运行所需的资源),实际工作由线程完成。

为什么说线程是cpu的最小调度单位?

因为实际需要cpu运算资源的就是线程。个人理解“最小调度单位”不应该理解为“线程是cpu调度的最小单位”,而应该理解为“线程是最小的调度cpu资源的单位”。

这里需要另外引申一个问题,不针对特定的OS(操作系统),这种讨论意义不大。具体可以参考:https://www.zhihu.com/question/29679344

2.锁的比喻不太合适

用厕所容量,来比喻锁,也不合适。因为线程间同步是为了防止竞争(就是说因同时修改,而导致的数据不一致)。

本质上讲,进程的内存空间是天然独立的,线程的内存空间是天然共享的。正因为如此,进程通信/线程同步才是系统编程的很大一块内容(也就是说,进程通信破坏了进程中内存空间的相互独立,线程同步破坏了同一进程中的内存共享。因为存在这些特殊情况,所以内存模型才复杂)。

3.最终结论

进程和线程简单而基本靠谱的定义如下:

  1. 进程:程序的一次执行;
  2. 线程:CPU的基本调度单位。

这两个概念虽然过于简单,但是完全可以为理解OS/线程/进程打下坚实的基础,我认为关于进程/线程的探讨,无论采用何种方式,都必须以这两句话为落脚点,才算靠谱。

你写的大多数文章相当出色。在以通俗的方式解释复杂的概念这方面,尤其出色。但是,我个人的经验,这种方式比较适合用来解释一个复杂概念的一个方面,或者从某个角度的理解。

想面面俱到的说明白一个复杂的系统,繁琐枯燥的概念,以及这些概念间的推演几乎不可避免。坦白说,在实际项目中大量用到线程/进程之前,我看过很多遍相关的概念,也写过小的示例程序,但真到了用的时候,还是发现之前没搞明白。

个人理解,网友herodot的意思是阮一峰dalao的理解已经足够通俗了,但是想要把概念抽象成具体事物,还是有些勉强,无法展示概念本身的相互联系(甚至可能有误导性)。所以,概念还是要从概念的层面进行理解。

(2)一处疑问

1.网友小涛的观点

进程是OS在程序运行时资源分配和调度的一个独立单元,一个进程可以开启一个或多个线程,进程抢到cpu时间片之后,如果该进程开启了多个线程,再由线程抢占cpu时间片,谁得到时间片谁执行。

看到这位网友小涛的观点后,根据学完《操作系统》留下的那一点记忆,我认为这种说法是靠谱的,然而有网友viho_he提出了不同的观点。

2.网友viho_he的观点

就我所知道到的OS(windows / linux / VxWorks / qnx)里,目前没有哪个是这样做的。
虽然目前没有不排除有这系统会这样去实现,然而从模型来说,这是个很糟糕的模型,因为这个模型增加了调度器的复杂度,调度器需要判断当前调度的单位是进程还是线程,并且做出不同的处理,加上优先级、时间片轮转、线程/进程运行状态等机制以后,此模型复杂度会显著增加,而到了SMP(同构并发多处理器)的系统上,此模型基本上已无能够工作的可能性。

即使在qnx这样鼓励用进程而非线程的微内核的OS上,文档仍然说到:Threads are scheduled globally across all processes.即调度的基本单位是线程,不是进程。调度器在调度任务时,只知道线程,不知道进程这个东西。对于进程的管理,是OS别的组件的事情。

到这里我就有些迷惑了,在进程获取时间片后,线程如何分配cpu资源?我翻了下《操作系统原理》没有找到相关的讨论。这是个值得研究的问题。

(3)“mutex和二元信号量并不同”(网友mdyang提出)

“不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,(在特殊情况下,括号内是我补的)完全可以用后者替代前者。”

网友mdyang认为,mutex和二元信号量并不同,可以参考stackoverflow上的讨论:http://stackoverflow.com/questions/62814/difference-between-binary-semaphore-and-mutex

个人认为:虽然mutex和semaphore可以实现相同的效果,但是两者的思想(目的?)是不同的。只有通过获得Mutex的线程才能释放Mutex,而信号量可以从任何其他线程(或进程)发出(区别在于是否可以由别的线程/进程解锁)。

mutex的强调“独占资源”(也有人认为mutex更加轻量),而semaphore强调“处理同步问题”(生产者消费者之类的一些同步问题),使用时应该根据不同的情景进行区分。

三、总结

操作系统的问题总是一环接一环的。个人认为最好跟随操作系统的发展史(同时也是硬件的发展史),结合书本,思考为什么要采用这样的解决方案,解决了什么问题。

顺带一提,个人认为java中进程的概念被弱化了。如果要跑某一条程序,往往只有java虚拟机的进程java.exe是进程,运行的程序都是该进程中的线程(虚拟机需要对程序负责到底),线程生存的空间只有方法区、堆….这点和c语言有一些区别,c编译出的每个exe都可以是一个独立的进程,拥有独立的地址空间,被cpu独立调度(当然在管理上就有更高的要求)。

发表评论

电子邮件地址不会被公开。 必填项已用*标注