进程与线程
说明:文档内容多数是从网络博客
整理搬运
而来,在每个章节的开头有整体标明参考来源,仅供个人参考学习。如有侵害到原创作者的利益,请联系m1yellow@163.com
处理。
进程与线程的区别
进程是资源分配的最小单位,线程是CPU调度的最小单位。
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见。
调度和切换:线程上下文切换比进程上下文切换要快得多。
(概念这东西,理工科还要死记硬背?就用自己的话表达出来就好了。)
通俗理解:
进程:一个程序运行就是一个进程。程序多开,运行的是多个进程。理论上一个封好包的应用程序运行后就只有一个进程,但程序分多个程序包或多个程序模块运行则有多个进程。
杀毒软件主程序及其功能模块以独立进程运行
谷歌浏览器主程序及插件以独立进程运行
线程:程序运行过程中,能同时处理多个业务,处理每个业务就是一个线程。
借助 Process Explorer 工具查看应用程序进程下的线程
做个简单的比喻:进程=火车,线程=车厢
线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
(https://www.zhihu.com/question/25532384/answer/411179772)
进程通信
管道
来看一条 Linux 的语句
netstat -tulnp | grep 8080
学过 Linux 命名的估计都懂这条语句的含义,其中”|“是管道的意思,它的作用就是把前一条命令的输出作为后一条命令的输入。在这里就是把 netstat -tulnp 的输出结果作为 grep 8080 这条命令的输入。如果两个进程要进行通信的话,就可以用这种管道来进行通信了,并且可以知道这条竖线是没有名字的,所以把这种通信方式称之为匿名管道。
并且这种通信方式是单向的,只能把第一个命令的输出作为第二个命令的输入,如果进程之间想要互相通信的话,那么需要创建两个管道。
有匿名管道,那也意味着有命名管道,下面来创建一个命名管道。
mkfifo test
这条命令创建了一个名字为 test 的命名管道。
接下来用一个进程向这个管道里面写数据,然后有另外一个进程把里面的数据读出来。
echo "this is a pipe" > test // 写数据
这个时候管道的内容没有被读出的话,那么这个命令就会一直停在这里,只有当另外一个进程把 test 里面的内容读出来的时候这条命令才会结束。接下来用另外一个进程来读取
cat < test // 读数据
可以看到,test 里面的数据被读取出来了。上一条命令也执行结束了。
从上面的例子可以看出,管道的通知机制类似于缓存,就像一个进程把数据放在某个缓存区域,然后等着另外一个进程去拿,并且是管道是单向传输的。
这种通信方式有什么缺点呢?显然,这种通信方式效率低下,你看,a 进程给 b 进程传输数据,只能等待 b 进程取了数据之后 a 进程才能返回。
所以管道不适合频繁通信的进程。当然,他也有它的优点,例如比较简单,能够保证的数据已经真的被其他进程拿走了。平时用 Linux 的时候,也算是经常用。
消息队列
那能不能把进程的数据放在某个内存之后就马上让进程返回呢?无需等待其他进程来取就返回呢?
答是可以的,可以用消息队列的通信模式来解决这个问题,例如 a 进程要给 b 进程发送消息,只需要把消息放在对应的消息队列里就行了,b 进程需要的时候再去对应的 消息队列里取出来。同理,b 进程要个 a 进程发送消息也是一样。这种通信方式也类似于缓存吧。
这种通信方式有缺点吗?答是有的,如果 a 进程发送的数据占的内存比较大,并且两个进程之间的通信特别频繁的话,消息队列模型就不大适合了。因为 a 发送的数据很大的话,意味**发送消息(拷贝)**这个过程需要花很多时间来读内存。
哪有没有什么解决方案呢?答是有的,请继续往下看。
共享内存
共享内存这个通信方式就可以很好着解决拷贝所消耗的时间了。
这个可能有人会问了,每个进程不是有自己的独立内存吗?两个进程怎么就可以共享一块内存了?
都知道,系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。那么可以让两个进程各自拿出一块虚拟地址空间来,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但有一部分却是映射到相同的物理内存,这就完成了内存共享机制了。
信号量
共享内存最大的问题是什么?没错,就是多进程竞争内存的问题,就像类似于平时说的线程安全问题。如何解决这个问题?这个时候的信号量就上场了。
信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
信号
类似于硬件中断,用于通知接收进程某个事件已经发生。
Socket
上面说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?
答是必须的,这个时候 Socket 这家伙就派上用场了,例如平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。
进程数据
标识符 状态 程序计数器 优先级 内存指针:保存程序代码、数据集 上下文数据:进程执行时处理器的寄存器中的数据 I/O 状态信息
进程调度算法
批处理系统
先来先服务 短作业优先 最短剩余时间优先(抢占)
交互系统
时间片轮转 优先级调度:优先级低的可以随着时间越来越高 多级反馈队列:每个队列的时间片不同