2021/6/4 并发编程-进程

进程理论:
1. 进程和程序

  程序:存放在硬盘上的一大堆代码,其状态是"死"的

  进程:程序正在运行的过程,其状态是"活"的

2. 进程调度算法

  对于多个进程的执行,都需要抢占CPU资源,而对于CPU仅存在一个的情况下,就需要使用进程调度算法来合理分配CPU资源

  先来先服务调度算法

    对于多个进程,按照先获取到CPU资源,就先执行的方式,再将该进程的任务完全执行后(包括IO操作),才会去执行下一个进程

    缺点:无法合理分配CPU资源,对于重要任务/进程,会因为没有抢占到CPU资源,从而延误

  短作业优先调度算法:

    对于进程的作业,处理及会计算出该进程的长度,从而根据长度来优先执行短的进程任务

    缺点:长作业会因为频繁的短作业从而一直得不到CPU资源

  时间片轮转调度算法:

    将CPU的处理时间划分成一个一个固定大小的时间片,时间片单位可以是几十毫秒之类,对不同的进程分配不同数量的时间片

    如果一个进程在被调度选中后,所分配的时间片执行完毕后,还未执行完任务,那么进程重新回到就绪队列,等待调度分配

    缺点:无法合理分配任务优先级

      1. 进程时间片用完,任务未执行完毕

      2. 进程时间片没用完,因为IO操作从而退出到就绪队列

      3. 新创建的高优先级进程,无法立刻被执行

  多级反馈队列调度算法:

    将CPU的处理时间分成多个队列,例如:高、中、低,其中高优先级队列的时间是中优先级队列的一半,以此类推

    对于多个进程,按照先入先出调度算法,进入高优先级队列,如果进程任务能够执行完毕,则释放资源,如果高优先级队列完毕进程任务依然无法执行完毕,则分配到中优先级队列

    对于高优先级队列中存在进程,那么立马退出中、低优先级队列,继而执行高优先级队列中的进程

    缺点:先入先出调度算法无法合理分配每个队列的资源

  时间片轮转+多级反馈队列

    将每个队列分成一个一个固定大小的时间片,然后按照调度算法分配给需要CPU资源的进程,再按照多级反馈队列机制来执行

3. 进程的三状态图

  创建 --提交--> 就绪态Ready --进程调度--> 运行态Running --释放--> 退出

  运行态Running --进程任务未执行完毕--> 就绪态Ready

  运行态Running --进程任务中途触发事件--> 阻塞态Blocked

  阻塞态Blocked --事件执行完毕--> 就绪态Ready

  事件:IO操作,请求缓冲区满等

  就绪态Ready:进程已经获取到除CPU资源以外的所有必要资源

  运行态Running:进程已经获取到CPU资源,开始执行任务

  阻塞态Blocked:进程任务中途触发事件,从而释放资源,等待事件执行完毕

4. 同步和异步

  同步:任务提交后,原地阻塞,等待任务返回结果

  异步:任务提交后,不会原地阻塞(通过回调机制/函数,获取到返回结果),直接执行其他任务

5. 阻塞和非阻塞

  阻塞:即处于进程运行三状态中的阻塞态

  非阻塞:即处于进程运行三状态中的就绪态或运行态

  最高效的组合:异步+非阻塞

  我们自己编写脚本,理想状态就是脚本一直处于就绪态和运行态

进程操作:

1. 创建进程的两种方式

  使用Process实例化

from multiprocessing import Process

def xxx(self, a):
  函数体

if __name__ == ‘__main__‘:

  p = Process(target=xxx, args=(a, ))

  p.start()

 

  类的继承

class xxx(Process):

  def run(self):  # 自动调用

    函数体

if __name__ == ‘__main__‘:
  p = xxx()

  p.start

2. Process类

from multiprocessing import Process

p = Process(

  target=函数名,

  args=(位置参数)

  kwargs={关键字参数}

  name=进程别名

  group=None  # 未使用

)

Process类属性:

  p.name  获取name参数,如果没有,则使用Process-N,N以1开始

  p.pid  获取进程id

  p.daemon  默认为False,True代表开启守护进程

  p.exitcode  获取进程退出状态码,如果进程正在运行则返回None,否则返回退出状态码(正常退出返回0)

  p.authkey  获取身份验证32位bytes,是通过os.urandom(32)随机生成的,用于底层身份验证

Process类方法:

  p.start()  创建/启动 进程

  p.join([timout])  主进程等待子进程执行结束,并调用wait方法回收子进程资源

  p.is_alive()  返回bool值,如果子进程存活返回True

  p.terminate()  强行终止子进程

current_process().pid  获取当前进程id

3. 进程间通信(IPC)

默认进程间不可通信,进程间相互隔离,这体现在内存空间上的隔离

不同进程存储各自的全局变量,进程间不会共享全局变量

实现进程间通信有两种方法:

  Queue队列

    = 管道+锁

  管道

Queue类

from multiprocessing import Queue

Queue类按照先入先出算法,对于队列中的数据,先放入的就会被先取出

Queue类的方法

q = Queue([int])  int代表队列长度,不填默认最大

q.qsize()  获取当前队列长度

q.empty()  如果队列位空返回True

q.full()  如果队列为空返回False

q.put(obj, block=True, timeout=None)

q.put_nowait()

q.get(block=True,timeout=None)

q.get_nowait()

参数讲解:

  block:设置是否阻塞,False不阻塞,直接报错,put报错queue.Full,get报错queue.Empty

  timeout:只有当block=True才有效,值为None代表无限大,一直原地阻塞,可以设置秒数,超过该值则直接报错

xx_nowait()  相当于block=False

JoinableQueue(Queue)继承于Queue类,且新增两个方法

.join()

.task_done()

JoinableQueue类存在一个计数器,当调用一次put方法时计数器+1,当调用一次task_done方法时,计数器-1

当计数器为0时,join方法停止阻塞,否则一直阻塞当前进程

 

Pipe类

Pipe类的实例化需要在进程创建代码之前,然后作为参数传递给进程对象

a, b =Pipe([duplex])

  duplex=True  全双工

  duplex=False  半双工

a.send(obj)

b.recv()

a.close()

recv方法会阻塞程序,如果管道中没有数据,则会一直阻塞等待数据

如果管道对端关闭端口,则会抛出EOFError异常

本端端口也要关闭端口

 

在windows中创建进程,需要将创建进程代码写入__name__语句中

因为,在windows中创建进程,默认会使用import类似的方法将本模块的代码导入到子进程中

因此,如果不加__name__语句,则会不停递归创建进程,从而引发错误

 

僵尸进程

  当子进程运行结束后,会留下一个Zombie僵尸进程的数据结构,该数据结构中包含进程ID、运行时间、退出状态等信息

  该Zombie数据结构会一直占用进程资源,只有当父进程调用wait/waitpid方法查看子进程的状态信息时,才会回收进程资源

  如果父进程存在循环,从而不能及时调用wait/waitpid方法,或者父进程突然死亡,那么就会一直留下僵尸进程,损耗进程资源

 

孤儿进程

  当父进程创建子进程后,突然死亡,此刻子进程还没有运行完毕,那么这就是一个孤儿进程

  在linux中,孤儿进程都会被init进程1收养,init进程会不停循环调用wait方法

  因此,当孤儿进程任务执行完毕后,会被立刻回收进程资源

 

守护进程

  守护进程会随着父进程的结束而终结

  守护进程不能继续创建子进程

from multiprocessing import Process

p = Process()

p.daemon = True开启守护进程

 

互斥锁

  如果存在多个进程对一个公共资源的访问、修改时,就极容易出现数据错误

  此刻就需要加锁处理,将并行变为串行,牺牲效率换取数据安全

from multiprocessing import Lock

mutex = Lock()

mutex.acquire()  加锁

mutex.release()  释放锁

  

2021/6/4 并发编程-进程

上一篇:Linux/ubunt关机、重启


下一篇:VMware搭建hadoop伪分布式环境