并发编程-进程
multipprocessing模块
multipprocessing模块是用来开启子进程,并在子进程执行的过程中我们制定的任务,该模块和threading的编程接口相似
方法介绍
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
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
process的使用
创建进程的两种方式
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
# 1 创建一个对象
p = Process(target=task, args=('jason',))
# 容器类型哪怕里面只有1个元素 建议要用逗号隔开
# 2 开启进程
p.start() # 告诉操作系统帮你创建一个进程 异步
print('主')
# 第二种方式 类的继承
from multiprocessing import Process
import time
class MyProcess(Process):
def run(self):
print(f'{self.name} is running')
time.sleep(3)
print(f'{self.name} is over')
if __name__ == '__main__':
p=MyProcess(target=MyProcess.run,args=('json',)).start()
print('主')
注意:
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
join方法的使用
from multiprocessing import Process
import time
def task(name, n):
print('%s is running'%name)
time.sleep(n)
print('%s is over'%name)
if __name__ == '__main__':
start_time = time.time()
p_list = []
for i in range(1, 4):
p = Process(target=task, args=('子进程%s'%i, i))
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('主', time.time() - start_time)
'''
子进程2 is running
子进程3 is running
子进程1 is running
子进程1 is over
子进程2 is over
子进程3 is over
主 3.1815736293792725
'''
# 当主进程等待子进程的时候,卡住的是主进程。此时所有的子进程已经交给操作系统开启了,子进程会正常运行的,等所有的子进程运行结束,主进程再运行。
# 每个子进程是在同时运行的,没有先后顺序之分。
# 所有主进程等待的时间应该是3s多,而不是1s+2s+3s
if __name__ == '__main__':
for i in range(1, 4):
p = Process(target=func, args=(f'p{i}', i))
p.start()
p.join()
print('主进程结束')
# 此时就是主进程要挨个等待每一个子进程的结束再创建并开启新的子进程。把p.join放在里面就是串行了,必须执行一次调用一次
继承之间的数据隔离问题
这是因为这是两个独立的名称空间。子进程有其自己的名称空间,只是修改其名称空间内全局变量的值,而不影响父进程自己名称空间全局变量的值。
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)
僵尸进程和孤儿进程
from multiprocessing import Process
import time
def run():
print('hello world')
time.sleep(3)
print('get out')
if __name__ == '__main__':
p = Process(target=run)
p.start()
print('主')
# 僵尸进程:
'''
当你开设了子程序之后,该进程结束后不会立即释放占用的进程号,因为要让父程序能够查看到它开设的子程序的一些基本信息占用的pid号,和运行时间,所有的进程都会步入僵尸进程,父进程不死并且在无限制的创建子进程,并且子进程也不会结束,如何回收子进程占用的pid号呢,有两种方式,父进程等待子进程运行结束,父类进程调用join方法
'''
# 孤儿进程
'''
子进程存活,父进程结束,这个就像操作系统会开设一个专门管理孤儿进程的回收相关资源
'''
守护进程
-
守护进程是一个在后台运行并且不受任何终端控制的进程,守护进程会在主进程执行代码结束后终止,守护进程内无法在开启子程序进程,否则会抛出异常
注意
进程之间是相互独立的,主程序代码运行结束,守护进程也会终止
from multiprocessing import Process import time def task(name): print(f'{name},88了您') time.sleep(3) print(f'{name},88了您1') if __name__ == '__main__': p=Process(target=task,args=('zc',)) p.daemon = True # 一定放在start前面,设置p为了守护进程,禁止p创建子进程,并且父进程代码执行结束,p也会终止 p.start() # print('嗝屁了')
互斥锁
就是进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或者打印终端,是没有问题的,但是共享就会有竞争,多进程操作同一个文件,会出现数据错六安的现象
但是加锁就会有牺牲:就是将并发编程串行,牺牲了效率保证了数据的安全
#就像我们买票一样,加入我们在10.30看到还有10张票,但是想一想还有很多票,我们过了30分钟再去买的时候发现票没有了,那是因为你访问的是10.30的数据,你在刷新的时候,就会没有了。如果我们不加互斥锁的话,上面有10张票,100个人再抢,那么这100个人显示的信息就是每个人都抢到了
from multiprocessing import Process, Lock
import json
import time
import random
# 查票
def search(i):
# 文件操作读取票数
with open('data','r',encoding='utf8') as f:
dic = json.load(f)
print('用户%s查询余票:%s'%(i, dic.get('ticket_num')))
# 字典取值不要用[]的形式 推荐使用get 你写的代码打死都不能报错!!!
# 买票 1.先查 2.再买
def buy(i):
# 先查票
with open('data','r',encoding='utf8') as f:
dic = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1,3))
# 判断当前是否有票
if dic.get('ticket_num') > 0:
# 修改数据库 买票
dic['ticket_num'] -= 1
# 写入数据库
with open('data','w',encoding='utf8') as f:
json.dump(dic,f)
print('用户%s买票成功'%i)
else:
print('用户%s买票失败'%i)
# 整合上面两个函数
def run(i, mutex):
search(i)
# 给买票环节加锁处理
# 抢锁
mutex.acquire()
buy(i)
# 释放锁
mutex.release()
if __name__ == '__main__':
# 在主进程中生成一把锁 让所有的子进程抢 谁先抢到谁先买票
mutex = Lock()
for i in range(1,11):
p = Process(target=run, args=(i, mutex))
p.start()
注意:
1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
2.锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)
进程间通信
队列Queue模块
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
创建队列的类
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
方法介绍
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
应用
'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''
from multiprocessing import Process,Queue
import time
q=Queue(3)
#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)
q.put(3)
q.put(3)
print(q.full()) #满了
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) #空了
注意
放五个数据,取六个数据,队列中如果已经没有数据的话 get方法会原地阻塞
那么如何让他报错呢,q.get_nowait() 或者 q.get(timeout=3)但是我们再写代码的时候求的是不出错,所以我们可以用try
try:
v6 = q.get(timeout=3)
print(v6)
except Exception as e:
print('一滴都没有了!')
IPC机制
from multiprocessing import Queue, Process
"""
研究思路
1.主进程跟子进程借助于队列通信
2.子进程跟子进程借助于队列通信
"""
def producer(q):
q.put('我是23号技师 很高兴为您服务')
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()
生产者和消费者
from multiprocessing import Process, Queue, JoinableQueue
import time
import random
def producer(name,food,q):
for i in range(5):
data = '%s生产了%s%s'%(name,food,i)
# 模拟延迟
time.sleep(random.randint(1,3))
print(data)
# 将数据放入 队列中
q.put(data)
def consumer(name,q):
# 消费者胃口很大 光盘行动
while True:
food = q.get() # 没有数据就会卡住
# 判断当前是否有结束的标识
# if food is None:break
time.sleep(random.randint(1,3))
print('%s吃了%s'%(name,food))
q.task_done() # 告诉队列你已经从里面取出了一个数据并且处理完毕了
if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
p1 = Process(target=producer,args=('大厨egon','包子',q))
p2 = Process(target=producer,args=('马叉虫tank','泔水',q))
c1 = Process(target=consumer,args=('春哥',q))
c2 = Process(target=consumer,args=('新哥',q))
p1.start()
p2.start()
# 将消费者设置成守护进程
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
p1.join()
p2.join()
# 等待生产者生产完毕之后 往队列中添加特定的结束符号
# q.put(None) # 肯定在所有生产者生产的数据的末尾
# q.put(None) # 肯定在所有生产者生产的数据的末尾
q.join() # 等待队列中所有的数据被取完再执行往下执行代码
"""
JoinableQueue 每当你往该队列中存入数据的时候 内部会有一个计数器+1
没当你调用task_done的时候 计数器-1
q.join() 当计数器为0的时候 才往后运行
"""
# 只要q.join执行完毕 说明消费者已经处理完数据了 消费者就没有存在的必要了
#生产者消费者模型总结
#程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
#引入生产者消费者模型为了解决的问题是:
平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
#如何实现:
生产者<-->队列<——>消费者
#生产者消费者模型实现类程序的解耦和

浙公网安备 33010602011771号