进程 线程 并行 并发
进程和线程的区别
进程是程序的执行实例,进程也是系统进行资源分配和调度的单位,每个进程都有自己独立的内存空间,地址空间等等资源,并且进程和进程之间也是相互独立的,一个进程发生崩溃并不会影响其他的进程。
线程其实是进程的一个部分,一个进程可以有多个线程,线程是CPU执行和调度的基本单位,线程只独享一些必不可少的资源如寄存器,栈。线程之间共享很多进程的资源。因此如果一个线程崩溃,会影响到其他线程甚至导致整个进程崩溃。
由于进程之间是独立的资源,而线程是共享很多资源。因此进程之间进行通信会涉及到很多其他的机制,比如消息队列,管道等。而一个进程内的多个线程进行通信可以直接通过全局变量进行共享数据,无需复杂的机制。
进程的上下文切换需要保存和恢复CPU寄存器,涉及到的开销很大,并且也需要更复杂的机制去保证进程之间的隔离性。然而线程的上下文切换仅需在操作系统和用户态之间就可以较为快速的切换。
并发和并行有什么区别?
并发是在同一时间段内管理多个任务,任务可以交替进行,但不一定真正同时执行。比如多个用户同时访问一个web服务器,服务器可以通过并发技术快速在多个请求之间进行切换,交替的处理每个请求,看起来所有的请求都得到了及时响应。并发可能会遇到任务间的切换和资源共享的问题。比如多个任务访问同一资源,如果没有合适的同步机制,会导致数据不一致。或者是死锁。多个任务如果都在等待对方释放资源就会导致无法继续执行。
并行是在同一时刻同时执行多个任务。多个任务必须在多个处理器上真正的同时运行。比如科学计算,或者视频的渲染。问题出在如何有划分任务上,一些任务可能有数据依赖,任务拆分比较困难。在并行系统中,多个任务之间需要进行数据交换,确保最终的一致性,这些同步和通信可能会带来性能开销,影响并行的效率
进程调度算法有哪些?
进程调度算法指的是对于在内存中可能存放着很多进程,当cpu数量小于进程数量,或者只是单核计算机。那么就需要有一个执行进程的先后顺序,进程调度算法就负责选择某个进程获得CPU使用权,从而实现对系统资源的高效利用。进程也分为计算密集型和IO密集型,计算密集型指的是占用CPU时间长,IO密集型指的是CPU计算时间短,而访问外接设备时间过长。这两者的特点是在进程调度算法中需要重点考虑的因素。
- 先来先服务。最简单的进程调度算法。从就绪队列中选择最优先进入队列的进程,运行该进程直到该进程退出或阻塞,就会继续选择就绪队列中下一个运行。但是这样会导致长任务阻塞队列
- 最短作业优先。先来先服务会造成长任务阻塞队列,所有先执行占用CPU时间最短的作业,这样就会提高系统的吞吐量,但是这样如果是频繁的短作业,就会阻塞长作业,导致长作业长期不运行
- 优先级调度。给每个进程分配一个优先级,优先执行优先级高的进程。优先级计算公式是 等待时间+要求服务时间/要求服务时间。可以看出等待时间相等情况下,要求服务时间越小,优先级越高,因此短作业优先级越高。要求服务时间相等,等待时间越大,优先级越高,因此长作业优先级增加。但是要求服务时间是不可预知的
- 时间片轮转调度。给每个进程分配一个时间片,每个进程的时间片是一样的。按照时间片轮转进行每一个进程。当时间片用完时该进程还没有执行完成,就会先将该进程的cpu释放出来给下一个进程,这个还未完成的进程需要等待到下一个进程。如果在时间片用完之前该进程就阻塞或者结束了,就会直接切换到下一个进程。
时间片太长会导致退化成先来先服务算法,短作业响应时间过长。时间片太短会导致过多的进程进行上下文切换,cpu效率降低。因此时间片一般设定为20-50ms - 多级反馈队列调度算法。使用多个优先级队列,每个队列有不同的优先级和不同的时间片,优先级越高,时间片越小。执行完高优先级队列的所有进程才会执行低优先级队列的进程。执行完某一个进程就会放入下一个优先级队列。当新进程加入优先级高的队列时,放入队尾,停止当前的进程并放入队尾,先执行优先级更高的队列。这种做法兼顾了效率和公平性
进程间的通信方式 IPC
- 管道:匿名管道,命名管道。linux中
|
的机制就是匿名管道。|
的功能就是将前一个命令的输出作为后一个命令的输入,因此管道的传输数据也是单向的,如果想相互通信就需要创建两个管道。命名管道是FIFO
,数据是先进先出的传输方式。想要在linux中创建命名管道使用mkfifo
命令1
mkfifo myPipe
管道通信效率低,不适合进程之间频繁地交换数据。匿名通道适用于父子进程之间交换数据,父进程产生数据,子进程进行处理。命名通道可以用于任何进程之间的通信
2. 消息队列。经常出现在多线程生产者消费者模型。通过操作系统内核维护一个消息链表。进程可以向消息队列中发送消息,也可以从消息队列中读取消息。消息队列中是由一个个独立的消息体(数据单元)组成,消息体由发送方和接收方约定数据类型。每个消息体都是固定大小的存储块。消息队列的生命周期随内核如果内核没有释放消息队列或关闭操作系统,消息队列会一直存在。但是消息队列不适合大数据传输,因为消息队列和消息体都会有最大长度的限制。并且存在用户态与内核态之间的数据拷贝开销
3. 共享内存:共享内存是将一块物理内存区域映射到需要共享的进程的虚拟内存地址空间上。这样多个进程可以通过访问该虚拟地址内存实现共享数据。这个是最快的方式,避免了用户态和内核态之间的数据拷贝。但是需要互斥锁,信号量这种机制避免多个进程同时读写共享内存时的数据竞争以及同时写造成的数据冲突
4. 信号量。信号量是计数型(允许多个进程访问有限的资源)或者二进制型(类似互斥锁,实现进程之间的互斥)。信号量一般和共享内存一起使用,实现进程之间的互斥和同步。
控制信号量的方式:
1. P操作,信号量-1. 进入共享资源之前使用
- if 信号量 < 0。表明资源被占用,进程需要阻塞等待
- if 信号量 >= 0. 表明资源可用,进程正常继续执行
2. V操作,信号量+1。 离开共享资源后使用。 pv操作必须成对出现
- if 信号量 <= 0. 表明有阻塞进程,会将进程唤醒
- if 信号量 > 0, 表明当前没有阻塞的进程
5. 信号: 进程间通信机制中唯一异步通信机制。可以在任何时候发送信号给某一进程,一旦有信号产生,就有下面几种用户进程对信号的处理方式
1. 执行默认操作,linux对每种信号都规定了默认操作。
2. 捕捉信号,将信号定义为一个信号处理函数,当信号发生时,我们就执行相应的信号处理函数。
3. 忽略信号。当进程不希望处理某些信号,就可以忽略该信号,不做任何处理
6. socket套接字。上面的通信方式都是一台主机上进行进程间通信。想要跨网络在不同主机上的进程通信,需要socket通信