多并发编程之进程
0x0进程的概念
0x0什么是进程?
正在进行的过程或任务,而执行该任务的是CPU。
0x1进程与程序的区别?
程序只是单纯的代码集,而程序一旦运行则该程序运行的过程则一个进程,在同一个程序运行多次的情况下出现的是多个进程,而不是一个进程,由此得出结论,每运行一次程序就会产生一个进程。
0x2 并发与并行
无论是并发还是并行,在用户看来都是在(同时)运行的,不管是进程还是线程都只是一个任务,真正执行的是CPU,而一个CPU同一时间只能执行一个任务。
并发:并发可以看做是伪并行,并发并不是真正意义上的同时运行,而是看上去是同时运行。实现方式:单个CPU+多道技术
并行:真正意义上的同时运行。实现方式:多个CPU
0x4 进程的创建
只要是硬件就需要操作系统来管理,然而又操作系统就必定有进程的概念,就需要有创建进程的方式。
对于通用的操作系统,需要有系统在运行过程中(操作系统本身上也是一个进程)有创建或撤销进程的能力,主要有4中创建进程的形式:
- 系统初始化(查看进程Linux中使用ps命令,Windows则可以直接使用任务管理器查看)
- 在一个进程运行过程中开启一个子进程(如Nginx开启多进程,os.fork,subprocess.Popen)
- 用户以交互式的请求创建进程(如打开一个应用程序)
- 批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经纯在的进程执行了一个用于创建进程的系统调用而创建的:
- 在Unix中该系统调用的是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像,同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
- 在Windows中该系统调用的是:CreateProcess,CreateProcess即负责进程的创建,也负责把正确的程序装入新的进程
关于创建子进程,Unix与Windows的区别:
- 相同的是:进程创建后,父进程与子进程有各自不同的地址空间(多道技术要求物理层面实现进程与进程间的内存隔离),任何一个进程的在其他地址空间中修改都不会影响到另一个进程
- 不同的是:在Unix中,子进程的初始地址空间是父进程的一个副本,子进程和父进程是可以有只读的内存共享区的。但对Windows而言,从一开始父进程与子进程的地址空间就是不同的
0x5 进程的结束
- 正常退出(用户正常关闭该程序)
- 出错退出(打开一个不存在的程序)
- 验证错误(执行非法指令,如引用不存在的内存,但可以通过异常捕获来处理)
- 被其他进程杀死(如Linux中的命令:kill -9 )
0x6进程的层次结构
无论是Unix还是Windows,进程只有一个父进程,区别是:
- 在Unix中的所有进程都是以init进程为根(顶级)组成树形结构。父子进程共同组成一个进程组
- 在Windows中,没有进程层次的概念,所有进程的地位都是相同的,唯一类似于进程层次的暗示,就是在创建进程时,父进程会得到一个句柄,该句柄可以用来控制子进程,但父进程有权限把该句柄交给其他进程
0x7 进程的状态
进程的三种状态:
- 运行态
- 阻塞态
- 就绪态
进程间的状态关系:
- 进程阻塞
- 调度程序选择其他进程
- 程序再次选择该进程
- 该进程运行
0x8 进程并发的实现
进程的并发实现在于,硬件中断一个正在运行的进程,把该进程的运行状态保存下来,因此操作系统维护一张表格:即进程表(Process table),每一个进程占用一个进程表项,该表项也被称为进程控制块
该表存放了进程的状态信息,因此该进程再次启动时,就像该进程没有被中断过一样》
0x1 开启进程
0x0 multiprocessing模块
python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。
需要注意的是:进程没有共享状态,进程修改的数据,改动仅限制于该进程内
0x1 Process类
创建进程的类:
Process(target=task,args=("子进程1",))
参数:
group:未设定时,默认为None target::调用者对象,即该进程要执行的代码块可以是函数形式 args:调用对象的参数,以元组形式传入。位置参数 kwargs:调用对象的参数,以字典传入 name:该进程的名称
方法:
p.start():启动进程 p.run():进程启动时运行的方法 p.terminate():强行终止进程 p.is_alive():查看该进程是否存活 ,存活返回Ture p.join():主线程等待该进程结束
属性:
p.daemon:默认为False,设置为True则表示该进程是守护进程 p.name:查看进程的名字 p.pid:查看进程pid
0x2 Process类的使用(开启进程)
在Windows中Process()必须放到 if __name__ == '__main__'下
开启子进程的两种方式:
! -*- coding:utf-8 -*- 方式一 from multiprocessing import Process import time def task(name): print("%s in running " %name) time.sleep(3) print("%s is done" %name) # windos 必须这种形式 if __name__ == '__main__': p1 = Process(target=task,args=("子进程1",)) p1.start() # 给操作系统发送信号 p2 = Process(target=task,kwargs={'name':"子进程2"}) p2.start() print("主进程")
# 方式二 from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print("%s is running " %self.name) time.sleep(3) print("%s is done" %self.name) if __name__ == '__main__': p = MyProcess("子进程1") p.start() print("主进程")
0x2互斥锁
互斥锁的作用:解决进程之的数据错乱
互斥锁的原理:把并发改成串行,降低效率确保数据的安全性
0X0 lOCK对象的使用
mutex = lock():示例化对象 mutex.acquire():加锁 mutex.release():释放锁
0x1互斥锁的使用
from multiprocessing import Process,Lock import time def task(name,mutex): mutex.acquire() print("%s 1"%name) time.sleep(1) print("%s 2"%name) time.sleep(1) print("%s 3"%name) mutex.release() if __name__ == '__main__': mutex = Lock() for i in range(3): p = Process(target=task,args=("进程%s"%i,mutex)) p.start()
0x2互斥锁的小案例:
模拟抢票
#! -*- coding:utf-8 -*- import json,time from multiprocessing import Process,Lock def search(name): """查询余票""" time.sleep(1) dic = json.load(open('db','r',encoding="utf-8")) print('<%s> 查询到余票[%s]张' %(name,dic['count'])) def get(name): """购买""" time.sleep(1) dic = json.load(open("db", 'r', encoding="utf-8")) if dic['count'] > 0: dic['count'] -= 1 time.sleep(3) json.dump(dic,open('db','w',encoding='utf-8')) print("%s 购买成功" %name) else: print("%s 购买失败" %name) def task(name,mutex): search(name) # mutex.acquire() # get(name) # mutex.release() with mutex: get(name) if __name__ == '__main__': mutex = Lock() for i in range(1,11): p = Process(target=task,args=("路人%s" %i,mutex)) p.start()
<路人1> 查询到余票[1]张 <路人2> 查询到余票[1]张 <路人4> 查询到余票[1]张 <路人3> 查询到余票[1]张 <路人5> 查询到余票[1]张 <路人7> 查询到余票[1]张 <路人6> 查询到余票[1]张 <路人8> 查询到余票[1]张 <路人9> 查询到余票[1]张 <路人10> 查询到余票[1]张 路人1 购买成功 路人2 购买失败 路人4 购买失败 路人3 购买失败 路人5 购买失败 路人7 购买失败 路人6 购买失败 路人8 购买失败 路人9 购买失败 路人10 购买失败
0X3互斥锁与Join的区别
虽然互斥锁都是把并行变成串行但唯一不同的是join是把该进程下的所有功能都变成串行,而互斥锁是吧局部的功能变成串行,这样既保证了数据的安全性,又保证了程序的执行效率,相对join而言
0x3队列
0x0 队列的概念:
由于进程之间是相互隔离的,要实现进程间的通信,必须要有一种解决方案,而multiprocess模块提供的两种形式的解决方法即:队列,与管道这两种方法都是进行消息传递的.而不同的是队列自动解决了锁的问题
0x1 队列类的使用
q = Queue(3)
参数:
maxsize:队列中最大数,不设置则无大小
方法:
q.put():插入数据 q.get():取出数据,并删除队列中的该数据 q.full():该队列是否已存放最大限制,
队列的使用
#! -*- coding:utf-8 -*- from multiprocessing import Queue q = Queue(3) q.put('helo') q.put({'a':1}) q.put([3,3,3]) print(q.full()) print(q.get()) print(q.get()) print(q.get()) print(q.empty()) print(q.get()) # q.put(1)
0x4生产者消费者模型
0X0 为什么要使用生产者消费者模型
生产者就是产生数据的任务,消费者是处理数据的任务,在并发编程中,如果生产者的处理熟读很快,而消费者处理速度很慢,那么生产者就必需等待消费者处理完毕才能继续生产数据.。同理如果消费者的能力大于生产者就必需等待生产者,因此未来解决该问题需要使用生产者消费者模式来解决该问题.
0x1 什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者与消费者的强耦合为题的。生产者与消费者并不直接通信,而是需要通过阻塞队列来进行通信,因此生产者生产完数据并不需要消费者等待,而是直接扔给阻塞队列,消费者不找生产者索要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,因此平衡了生产者与消费者的处理能力.
0x2 生产者与消费者模型的实现
#! -*- coding:utf-8 -*- from multiprocessing import Process,Queue import time def producer(q): for i in range(10): res='包子%s' %i time.sleep(0.5) print('生产者生产了%s' %res) q.put(res) def consumer(q): while True: res=q.get() if res is None:break time.sleep(1) print('消费者吃了%s' % res) if __name__ == '__main__': #容器 q=Queue() #生产者们 p1=Process(target=producer,args=(q,)) p2=Process(target=producer,args=(q,)) p3=Process(target=producer,args=(q,)) #消费者们 c1=Process(target=consumer,args=(q,)) c2=Process(target=consumer,args=(q,)) p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() p2.join() p3.join() q.put(None) q.put(None) print('主')
0x3 JoinableQueue的使用
参数:
maxsize:队列的最大存放数
方法:
q.task_done():使用者发送信号,表示q.get()的返回项已被处理。此方法的使用数大于maxsize的值,
会抛出异常ValueErro q.join():生产者调用该方法进行阻塞,直到队列中所有项目被处理,阻塞持续到队列中所有项目都调用
q.task_done()
通过JoinableQueue解决上面的问题
#! -*- coding:utf-8 -*- from multiprocessing import Process,JoinableQueue import time def producer(q): for i in range(2): res = "包子%s" %i time.sleep(0.5) print("生产者生产了%s" %res) q.put(res) q.join() def consumer(q): while True: res = q.get() if res is None:break time.sleep(1) print("消费者吃了%s" %res) q.task_done() if __name__ == '__main__': q = JoinableQueue() p1 = Process(target=producer,args=(q,)) p2 = Process(target=producer, args=(q,)) p3 = Process(target=producer, args=(q,)) c1 = Process(target=consumer,args=(q,)) c2 = Process(target=consumer,args=(q,)) p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() p2.join() p3.join() print("主线程")