并发编程之多进程
Posted wanlei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程之多进程相关的知识,希望对你有一定的参考价值。
一、什么是进程
一个正在运行的程序称之为进程
是一种抽象概念 表示一个执行某件事情的过程,进程的概念 起源于操作系统
第一代计算机 程序是固定 无法修改 某种计算机只能干某种活
第二代批处理系统 需要人工参与 将程序攒成一批 统一执行,串行执行 提高计算机的的利用率 但是调试麻烦
第三代计算机 为了更好利用计算机资源,产生了
多道技术: (重点)
1.空间复用
内存分割为多个区域 每个区域存储不同的应用程序
2.时间的复用
1.当一个程序遇到了I/O操作时 会切换到其他程序 (切换前需要保存当前运行状态 以便恢复执行),提高效率
2.当你的应用程序执行时间过长 操作系统会强行切走 以保证其他程序也能正常运行 当然因为cpu速度贼快 用户感觉不到,降低效率
3.有一个优先级更高的任务需要处理 此时也会切走,降低了效率
我们编写程序时 只能尽量减少I/O操作
总的来说 有了多道技术之后 操作系统可以同时运行多个程序吧 这种情形称之为并发
但是本质好 这些程序还是一个一个排队执行。
#一 操作系统的作用: 1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口 2:管理、调度进程,并且将多个进程对硬件的竞争变得有序 #二 多道技术: 1.产生背景:针对单核,实现并发 ps: 现在的主机一般是多核,那么每个核都会利用多道技术 有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个 cpu中的任意一个,具体由操作系统调度算法决定。 2.空间上的复用:如内存中同时有多道程序 3.时间上的复用:复用一个cpu的时间片 强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样 才能保证下次切换回来时,能基于上次切走的位置继续运行
进程的并行与并发
并发 看起来像是同时运行中 本质是不停切换执行 多个进程随机执行
并行 同一时刻 多个进程 同时进行 只有多核处理器才有真正的并行
区别:
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
串行 一个一个 依次执行排队
阻塞 遇到了I/O操作 看起来就是代码卡住了,非阻塞 不会卡住代码的执行
阻塞 和 非阻塞 说的是同一个进程的情况下
同步 一个调用必须获得返回结果才能继续执行
异步 一个调用发起后 发起方不需要等待它的返回结果
同步和异步 必须存在多个进程(线程)
无论是进程还是线程都是两条独立的执行路径
多进程的执行顺序
主进程必然先执行,子进程应该在主进程执行后执行,一旦子进程启动了 后续的顺序就无法控制了
python如何使用多进程
1.直接创建Process对象 同时传入要做的事情就是一个函数
p = Process(taget=一个函数,args=(函数的参数))
p.start() 让操作系统启动这个进程
2.创建一个类 继承自Process 把要做的任务放在run方法中
常用属性
start 开启进程
join 父进程等待子进程
name 进程名称
is_alive是否存活
terminate 终止进程
pid 获取进程id
启动进程的方式
1.系统初始化 会产生一个根进程
2.用户的交互请求 鼠标双击某个程序
3.在一个进程中 发起了系统调用启动了另一个进程
4.批处理作业开始 某些专用计算机可能还在使用
不同操作系统创建进程的方式不同
unix < centos mac linux
完全拷贝父进程的所有数据 子进程可以访问父进程的数据吗?不可以 但是可以访问拷贝过来数据副本
windows
创建子进程 加载父进程中所有可执行的文件
二、在python程序中的进程操作
一 multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu\_count()查看),在python中大部分情况需要使用多进程。
Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内
二 Process类的介绍
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,可用来开启一个子进程 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None target表示调用对象,即子进程要执行的任务 args表示调用对象的位置参数元组,args=(1,2,‘egon‘,) kwargs表示调用对象的字典,kwargs={‘name‘:‘egon‘,‘age‘:18} name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
三 Process类的使用
注意:在windows中Process()必须放到# if __name__ == ‘__main__‘:下
创建并开启子进程的方式
import time import random from multiprocessing import Process def piao(name): print(‘%s piaoing‘ %name) time.sleep(random.randrange(1,5)) print(‘%s piao end‘ %name) if __name__ == ‘__main__‘: #实例化得到四个对象 p1=Process(target=piao,args=(‘egon‘,)) #必须加,号 p2=Process(target=piao,args=(‘alex‘,)) p3=Process(target=piao,args=(‘wupeqi‘,)) p4=Process(target=piao,args=(‘yuanhao‘,)) #调用对象下的方法,开启四个进程 p1.start() p2.start() p3.start() p4.start() print(‘主‘)
import time import random from multiprocessing import Process class Piao(Process): def __init__(self,name): super().__init__() self.name=name def run(self): print(‘%s piaoing‘ %self.name) time.sleep(random.randrange(1,5)) print(‘%s piao end‘ %self.name) if __name__ == ‘__main__‘: #实例化得到四个对象 p1=Piao(‘egon‘) p2=Piao(‘alex‘) p3=Piao(‘wupeiqi‘) p4=Piao(‘yuanhao‘) #调用对象下的方法,开启四个进程 p1.start() #start会自动调用run p2.start() p3.start() p4.start() print(‘主‘)
四、Process对象的join方法
在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况
情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用
import time from multiprocessing import Process def f(name): print(‘hello‘, name) time.sleep(1) if __name__ == ‘__main__‘: p_lst = [] for i in range(5): p = Process(target=f, args=(‘bob‘,)) p.start() p_lst.append(p) p.join() # [p.join() for p in p_lst] print(‘父进程在执行‘)
import time from multiprocessing import Process def f(name): print(‘hello‘, name) time.sleep(1) if __name__ == ‘__main__‘: p_lst = [] for i in range(5): p = Process(target=f, args=(‘bob‘,)) p.start() p_lst.append(p) # [p.join() for p in p_lst] print(‘父进程在执行‘)
除了上面这些开启进程的方法,还有一种以继承Process类的形式开启进程的方式
import os from multiprocessing import Process class MyProcess(Process): def __init__(self,name): super().__init__() self.name=name def run(self): print(os.getpid()) print(‘%s 正在和女主播聊天‘ %self.name) p1=MyProcess(‘wupeiqi‘) p2=MyProcess(‘yuanhao‘) p3=MyProcess(‘nezha‘) p1.start() #start会自动调用run p2.start() # p2.run() p3.start() p1.join() p2.join() p3.join() print(‘主线程‘)
进程之间的数据隔离问题
from multiprocessing import Process def work(): global n n=0 print(‘子进程内: ‘,n) if __name__ == ‘__main__‘: n = 100 p=Process(target=work) p.start() print(‘主进程内: ‘,n)
三、守护进程
主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了。
关于守护进程需要强调两点:
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随即终止
from multiprocessing import Process import time import random def task(name): print(‘%s is piaoing‘ %name) time.sleep(random.randrange(1,3)) print(‘%s is piao end‘ %name) if __name__ == ‘__main__‘: p=Process(target=task,args=(‘egon‘,)) p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行 p.start() print(‘主‘) #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
守护 就是看着 陪着 在代码中 进程只能由进程类守护
一个进程守护者另一个进程 指的是两个进程之间的关联关系
特点:守护进程(妃子) 在被守护进程(皇帝)死亡时 会跟随被守护进程死亡
什么时候需要使用守护进程?
例如: qq中有个下载视频 应该用子进程去做 但是 下载的过程中 qq退出 那么下载也没必要继续了
四、互斥锁
from multiprocessing import Process,Lock # 进程间 内存空间是相互独立的 def task1(lock): lock.acquire() for i in range(10000): print("===") lock.release() def task2(lock): lock.acquire() for i in range(10000): print("===============") lock.release() def task3(lock): lock.acquire() for i in range(10000): print("======================================") lock.release() if __name__ == ‘__main__‘: # 买了一把锁 mutex = Lock() # for i in range(10): # p = Process(target=) p1 = Process(target=task1,args=(mutex,)) p2 = Process(target=task2,args=(mutex,)) p3 = Process(target=task3,args=(mutex,)) # p1.start() # p1.join() # p2.start() # p2.join() # p3.start() # p3.join() p1.start() p2.start() p3.start() print("over!")
# 什么时候用锁? # 当多个进程 同时读写同一份数据 数据很可能就被搞坏了 # 第一个进程写了一个中文字符的一个字节 cpu被切到另一个进程 # 另一个进程也写了一个中文字符的一个字节 # 最后文件解码失败 # 问题之所以出现 是因为并发 无法控住顺序 # 目前可以使用join来将所有进程并发改为串行 # 与join的区别? # 多个进程并发的访问了同一个资源 将导致资源竞争(同时读取不会产生问题 同时修改才会出问题) # 第一个方案 加上join 但是这样就导致了 不公平 相当于 上厕所得按照颜值来 # 第二个方案 加锁 谁先抢到资源谁先处理[ # 相同点: 都变成了串行 # 不同点: # 1.join顺序固定 锁顺序不固定! # 2.join使整个进程的任务全部串行 而锁可以指定哪些代码要串行 # 锁使是什么? # 锁本质上就是一个bool类型的标识符 大家(多个进程) 在执行任务之前先判断标识符 # 互斥锁 两个进程相互排斥 # 注意 要想锁住资源必须保证 大家拿到锁是同一把 # 怎么使用? # 在需要加锁的地方 lock.acquire() 表示锁定 # 在代码执行完后 一定要lock.release() 表示释放锁 # lock.acquire() # 放需要竞争资源的代码 (同时写入数据) # lock.release()
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或者打印终端是没有问题的,但是带来的是竞争,竞争带来的结果是错乱,如下:多个进程模拟多个人执行抢票任务
#文件db.txt的内容为:{"count":1} #注意一定要用双引号,不然json无法识别 from multiprocessing import Process import time,json def search(name): dic=json.load(open(‘db.txt‘)) time.sleep(1) print(‘