目录:
1.进程/线程/协程
2.多进程 多线程
3.进程状态的切换
4.进程调度算法
5.进程同步
6.进程间的通信
进程/线程/协程:
进程是资源分配的基本单位。
进程控制块(Process Control Block,PCB)描述进程的基本信息和运行状态,进程的创建和撤销都是由PCB来操作。
线程是独立调度的基本单位。由(程序计数器,堆栈,和一组寄存器以及一个标识符组成)
协程又称(微线程)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。
一个进程可以有多个线程,共享进程资源。
进程与线程的区别:
- 拥有资源
- 进程是资源分配的基本单元,但是线程不拥有资源,线程可以访问所属进程的资源
- 调度
- 线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程的切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换
- 系统开销
- 创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等,所付出的开销远大于创建或撤销线程时的开销。
- 通信方面
- 线程间可以通过直接读写同一进程中的数据进行通信,进程通信需要借助IPC
协程的优势:
- 协程最大的优势就是协程有极高的执行效率,因为子程序的切换不是进程的切换,而是程序自身控制,因此,没有线程切换的开销,和多线程相比,线程数量越多,协程显现的优势越大。
- 与多线程相比协程不需要锁机制,因为只有一个线程,也不会存在冲突的情况,控制共享资源时不需要加锁,只需要判断状态就好了,所以执行效率要比线程高的很多。
协程如何利用多核cpu:多进程+协程。既充分利用多核,又充分发挥协程的高效率,可能获得极高的性能。
多线程和多进程:
区别:
数据共享同步
- 多进程:数据是分开的:共享复杂,需要IPC;同步简单
- 多线程:多线程共享进程数据简单,同步复杂
内存CPU
- 多进程:占用内存多,切换复杂,CPU利用率低
- 多线程:占用内存少,切换简单,CPU利用率高
创建销毁切换:
1:多进程:复杂,速度慢。
2:所线程:简单,速度快
编程调试:
1:.多进程:编程简单调试简单
2:多线程:编程复杂,调试复杂
可靠性:
1:多进程:进程间通信互不影响
2:多线程:一个线程挂掉将导致整个进程挂掉
优缺点:
多进程:
- 优点:内存隔离,单个进程的异常不会导致其他进程的奔溃,方便调试。
- 缺点:进程间的操作开销比线程大
- 适合使用场合:目标的子功能之间的交互少,密集CPU任务,需要充分适用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。
多线程:
1 优点:提高了系统的并行性,开销小,对cup的利用率高
2 缺点:
1.在一个进程内的所有线程都共享全局变量,很方便在多个线程间共享数据,缺点就是,线程是对全局变量的改变可能造成多线程之间的全局变量发生混乱(即多线程是非安全的)
解决线程全局变量发生混乱的方式:互斥锁(互斥锁为资源引入一个状态:锁定/非锁定)某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程再次锁定该资源。互斥锁保证了每次只有一个线程进程写操作,从而保证了多线程情况下数据的正确性。
3 使用场合:存在大量的IO操作(IO操作不占用CPU从硬盘,网络,内存读取数据都算IO),网络等耗时操作。
4 在python中的缺点:Python中有全局解释器锁(GIL)的限制;并不是同一时刻,当线程的数目增多时需要进行切换,也会导致程序的整体性能下降。
Python的GIL(全局解释器锁)
- GIL本质是一个互斥锁,可以防止多个线程同时执行python代码。他是一个只由线程保持的锁,当一个代码在执行前必须获取GIL锁。优点在于,当它被锁定时没有别的线程可以同时运行代码,一定程度上避免了线程间的冲突。
进程状态的切换:
1.进程的三种状态:
运行态:指该进程正在被CPU调度运行。
就绪态:指该进程满足被cpu调度的所有条件但是并没有被调度执行。
阻塞态:指该进程正在等待某事件的发生之后才可以继续被cpu调度运行。
- 三种状态之间的切换;
- 就绪->运行 :当操作系统的调度程序从就绪态的链表中调度一个进程时,该进程的进程状态就会被切换运行,与此同时cpu即开始进入运行状态
- 运行->就绪:当一个正在运行的进程的时间片到达时,cpu必须调度下一个进程,此时处于运行状态的进程被切换到就绪态,并重新进入操作系统的就绪态的进程链表。
- 运行->阻塞:当cpu正在运行一个进程时,该进程此时需要等待一个进程的完成才能继续运行,这时操作系统就会将该进程状态切换位阻塞状态,知道进程所需要的等待条件完成
- 阻塞->就绪:进程从运行态直接切换为阻塞态,当进程所需要的时间完成后,操作系统不会直接将该进程的状态切换为运行态,而是将该进程状态切换为就绪态,等待cpu的调度。
- Linux操作系统的进程状态:
- R运行状态:并不意味着进程一定是运行状态,也可能是在运行队列中。
- S睡眠状态:进程在等待事件完成
- D磁盘睡眠状态:不可终端睡眠(深度睡眠,不可被唤醒,通常发生在磁盘的写入)
- T停止状态:可以通过发送SIFSTOP信号给进程来停止进程,可以发送SIGCONT信号让进程继续运行
- X死亡状态:该进程是返回状态,在任务列表中看不到
- Z僵尸状态:子进程退出,父进程还在运行,但是父进程没有读到子进程的退出状态,子进程成为僵尸状态
- T追踪停止状态
- 不得不说的僵尸进程与孤儿进程
进程调度算法:
不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法
- 批处理系统
- 先来先服务:first-come first-serverd(FCFS)
- 非抢占式调度算法,按照请求顺序进行调度
- 有利于长作业,不利于短作业,短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长事件,造成了短左右等待的时间过长
- 短作业优先shortest job first(SJF)
- 非抢占式的调度算法,短作业优先的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将他们调入内存运行。而短作业优先是从就绪队列中选出一个估计运行时间最短的进程,将处理及分配给它,使它立即执行并一直执行到完成。,。
- 短作业优先算法的缺点
- 不利于长作业,长作业的周期会明显增加
- 没有考虑作业的紧迫性,不能保证紧迫作业及时得到处理
- 最短剩余时间优先:
- 最短优先为抢占式,按所有进程中剩余时间最短的进行调度,当新的作业到达时,qi整个运行时间与党当前进程的剩余时间比较,谁短谁先。
- 先来先服务:first-come first-serverd(FCFS)
- 交互系统:
交互系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应
-
- .时间片轮转:
- 将所有就需进程按FCFS(先来先服务)的原则排成一个队列,每次调度时,把cpu时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,计时器会中断,调度进程便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把cpu时间分配给队首的进程。
- 时间片轮转算法的效率和时间片的大小有很大关系
- 时间太短会使进程切换太频繁,在进程切换上会花费时间,时间太长,实时性就不能得到保障。
- 优先级调度:
- 为每个进程分配一个优先级,按优先级进行调度。
- 为了防止低的优先级的进程永远得不到调度,可以随着时间的推移增加等待进程的优先级
- 多级反馈队列调度算法
- 多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,可以分为优先级不同且大小不同的队列,最短的优先级最高,当一个进程进入一个队列中如果在此队列没有执行完毕将会按FCFS来进入下一个队列中以此类推,最上面的优先权最高,因此只有上一个队列中没有进程在排队,才能调度当前队列上的进程,(可以看出多级反馈队列是时间片轮转,和优先级调度的结合)
- .时间片轮转:
进程同步
1. 临界区
对临界资源进行访问的那段代码称为临界区
优点:保证在某一时刻只有一个线程能访问数据的简便方法。
缺点:虽临界区同步速度和快,但却只能用来同步本进程内的线程,而不可用来同步多个进程的线程
2 同步互斥
同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后顺序
互斥:多个进程在同一时刻只有一个进程能进入临界区
3 信号量
信号量是一整个整型变量,可以对其执行down和up操作,也就是常见的p,v操作。
Down:如果信号量大于0,执行-1操作,如果信号量等于0,进程睡眠,等待信号量大于0
Up:对信号量执行+1操作,唤醒睡眠进程让其完成down操作
优点:适用于对Socket程序中线程的同步。
缺点:信号量机制必须有公共的内存,不能用于分布式的操作系统,这是最大的弱点。
信号量机制功能强大,但使用时对信号量的操作分散,而且难以控制,读写维护也很困难。
4 事件
用来通知线程有一些事件已经发生,从而启动后继任务的开始。
优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作(跨进程同步)
进程间的通信
- 管道/匿名管道(pipe)
- 管道是通过调用pipe函数创建,管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常是指父子进程关系。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。当缓冲区空或者写满时,有一定的规则控制相应的读写进程进入缓冲队列,让缓冲区有位置时,将唤醒等待队列中的进程。
- 单独构成一种独立的文件系统,存在内存中
- 管道的实质:实质是一个循环队列,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。一个数据只能被读一次,读出来以后缓冲区就不复存在了
- 管道的局限
- 只支持单向数据流
- 只能用于具有血缘关系的进程之间
- 管道的缓冲区时有限的(管道存在与内存中,在管道创建时,为缓冲区分配一个页面的大小)
- 管道所传送的是无格式的字节流,所以要求管道的读写操作必须事先约定好数据的格式
- 有名管道(FIFO)
- 有名管道是为了克服匿名管道只能同于血缘关系进程间的通信,有名管道不同于匿名管道之处在于它提供了一个路径名与之关联
- 有名管道严格遵循先进先出,对匿名管道及有名管道的读总是从开始处返回数据,对他们的写则是把数据添加到末尾。有名管道的名字存在于文件系统中,内容存放在内存中。
- 匿名管道和有名管道总结
- 管道是特殊的文件类型,在满足先入先出的原则条件下可以进行读写,但不能进行定位读写
- 匿名管道是单向的,只能在有亲缘关系的进程间通信;有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信
- 无名管道阻塞问题:无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过最大值,写操作将阻塞,如果管道没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出
- 有名管道的阻塞问题:有名管道在打开时需要确定对方的存在,否则及将阻塞。即以读方式打开某管道,再此之前必须一个进程以写方式打开管道,否则阻塞,此外可以用读写模式打开有名管道,即当前进程读,当前进程写不会阻塞。
- 消息队列:
- 消息队列提供了一种从一个进程向另一个进程发送一个进程块的方法。每个数据快都被认为含有一个类型,接受进程可以独立的接受含有不同数据类型的数据结构。通过发送消息来避免命名管道的同步和阻塞问题,消息队列与命名管道一样每个数据块都有最大的长度限制。
- 消息队列与命名管道的比较:
- 消息队列也可以独立发送和接收进程而存在,从而消除在同步命名管道的打开和关闭时可能产生的困难。
- 同时通过发送消息可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供方法。
- 接收程序可以通过消息类型有选择的接收数据,而不是向命名管道只能默认的接收。
- 信号量(本质就是一个计数器,用于为多个进程提供对共享数据的访问):
- 看信号量之前首先要搞清楚一下几个概念:
- 进程互斥:两个或两个以上的进程不能同时进入同一组共享资源的临界区,否则可能发生与时间有关的错误,也就是说一个进程在访问临界资源,另一个进程必须等待。
- 进程同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系
- 临界资源:系统中某些资源只允许一个进程使用。
- 临界区:再进程中涉及到互斥资源的临界区
- 原子性:不可被中断的操作。
什么是信号量:为了防止出现多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,他可以通过生产并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。而信号量可以提供这种访问机制,让一个临界区同一时间只有一个线程访问它,也就是说信号量是用来协调进程对共享资源的访问。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用:-
信号量>0:表示资源可用于请求
-
信号量=0:无资源可用,进程会进入睡眠状态直至资源可用
-
当信号量不再使用一个信号量控制共享资源时,信号量+1,对信号量的值进行增加操作均为原子操作。而在信号量的创建及初始化上,不能保证操作均为原子性。
-
信号量的生命周期:信号量的生命周期与消息队列是一样的不随进程的结束而结束,而是随内核的。
-
信号量的工作原理:
-
P(sv):如果sv的值大于0,就对其减1;如果它的值为零,就挂起该进程的执行。
-
V(sv):如果有其他进程因等待sv而被挂起,就让他恢复运行;如果没有进程因等待sv而挂起,就给他加1
-
举例说明:两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1.而第二个进程将被阻止进入临界区,因为当他试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区执行V(sv)时释放信号量,这时第二个进程可以恢复执行。
-
二元信号量:时最简单的一种锁,它只有两种状态(占有与非占有)。它适合只能被唯一一个线程访问资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量的线程会获得该锁,并将二元信号量置为占用状态,其他所有试图获取该二元信号量的进程将会等待,直到该锁被释放。
-
- 看信号量之前首先要搞清楚一下几个概念:
-
共享内存:
-
共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存,进程可以将同一段共享内存连接到他们自己的地址空间中,所有进程可以访问共享内存中地址。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
-
因为数据不需要在进程之间复制,所以只最快的一种IPC 。使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。若一个进程正在向共享内存区写入数据,则在其他做完这一步操作前,别的进程不应当区读,写这些数据。需要使用信号量用来同步对共享存储的访问。多个进程可以及那个同一个文件映射到它们的地址空间从而实现共享内存。
-
-
套接字:
-
与其他进程不同的是,它可用于不同机器间的进程通信。socket是套接字的一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行,也就是说它可以让不同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样套接字明确地将客户端和服务端分开来。
-
套接字的三种属性:套接字的域,套接字类型,套接字协议。
-
套接字域:指定套接字通信中使用的网络介质。做常见的就是Internet网络
-
套接字类型:Internet提供两种通信机制:流和数据报
-
套接字协议:只要底层的传输机制允许不止一个协议来提供要求的套接字类型,可以为套接字选择一个特定的协议。通常只需要默认值。
-
-