从小白到小黑 python学习之旅 日常总结 38~39(并发编程之多进程 操作2)
开启进程的2种方式
第一种方式
from multiprocessing import Process import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) """ windows操作系统下 创建进程一定要在main内创建 因为windows下创建进程类似于模块导入的方式 会从上往下依次执行代码 linux中则是直接将代码完整的拷贝一份 """ 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 __init__(self): # super().__init__() def run(self): print('hello bf girl') time.sleep(1) print('get out!') if __name__ == '__main__': p = MyProcess() p.start() # 自动调run 必须是run固定写法 print('主')
join方法(让 调用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__': p1 = Process(target=task, args=('jason', 1)) p2 = Process(target=task, args=('egon', 2)) p3 = Process(target=task, args=('tank', 3)) start_time = time.time() p1.start() p2.start() p3.start() # 仅仅是告诉操作系统要创建进程 p1.join() p2.join() p3.join() print('主', time.time() - start_time) #jason is running #egon is running #tank is running #jason is over #egon is over #tank is over #主 4.613263845443726
进程之间内存空间是隔离的
from multiprocessing import Process money = 100 def task(): global money # 局部修改全局 money = 666 print('子',money) if __name__ == '__main__': p = Process(target=task) p.start() # 开启子进程 p.join() # 等子进程运行完 在运行主进程 print(money) #进程的内存空间是隔离的所以子进程无法改变主进程的全局空间 #子 666 #100 # 所以 主进程的money的值还是100
进程对象及其他方法
from multiprocessing import Process, current_process current_process().pid # 查看当前进程的进程号 import os os.getpid() # 查看当前进程进程号 os.getppid() # 查看当前进程的父进程进程号 p.terminate() # 杀死当前进程 # 是告诉操作系统帮你去杀死当前进程 但是需要一定的时间 而代码的运行速度极快 print(p.is_alive()) # 判断当前进程是否存活
""" 一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端的呢? 计算机会给每一个运行的进程分配一个PID号
如何查看 windows电脑 进入cmd输入tasklist即可查看 tasklist |findstr PID查看具体的进程 mac电脑 进入终端之后输入ps aux ps aux|grep PID查看具体的进程
""" from multiprocessing import Process, current_process import time import os def task(): # print('%s is running'%current_process().pid) # 查看子进程的进程号 print('子进程号%s'%os.getpid()) # 查看子进程的进程号 print('主进程号%s'%os.getppid()) # 查看主进程的进程号 if __name__ == '__main__': p = Process(target=task) p.start() # p.terminate() # 杀死当前进程 # 是告诉操作系统帮你去杀死当前进程 但是需要一定的时间 而代码的运行速度极快 所以要加一点延迟时间 # time.sleep(0.1) # print(p.is_alive()) # 判断当前进程是否存活 """ 一般情况下我们会默认将 存储布尔值的变量名 和返回的结果是布尔值的方法名 都起成以is_开头 """ # print('主',current_process().pid) print('主',os.getpid()) # print('主主',os.getppid()) # 获取父进程的pid号 pycharm #主 16180 #子进程号18104 #主进程号16180
僵尸进程与孤儿进程
僵尸进程
子进程死了但是没有死透
当你开设了子进程之后 该进程死后不会立刻释放占用的进程号
因为我要让父进程能够查看到它开设的子进程的一些基本信息 占用的pid号 运行时间。。。
所有的进程都会步入僵尸进程
等待父进程正常结束后会调用wait/waitpid去回收僵尸进程 但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的 解决方法一:杀死父进程 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源 #“儿童福利院”就是init
守护进程
当子进程执行的任务在父进程代码运行完毕就没有存在的必要了,那该子进程就应该被设置为守护进程
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process import time def task(): print('1') time.sleep(1) print('2') time.sleep(1) print('3') time.sleep(1) print('4') time.sleep(1) print('5') if __name__ == '__main__': p = Process(target=task) p.daemon = True # 将进程p设置成守护进程 这一句一定要放在start方法上面才有效否则会直接报错 p.start() time.sleep(3) print('皇帝jason寿终正寝') #1 #2 #皇帝jason寿终正寝 #主进程执行完了 子进程也随之停止执行(死了)
互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,
而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,解决方式就是加锁处理: **将并发变成串行,牺牲效率但是保证了数据的安全**
#文件data {"ticket_num": 0} 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 你写的代码打死都不能报错!!! # 买票 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,6): p = Process(target=run, args=(i, mutex)) p.start() #用户1查询余票:1 #用户2查询余票:1 #用户4查询余票:1 #用户5查询余票:1 #用户3查询余票:1 #用户1买票成功 #用户2买票失败 #用户4买票失败 #用户5买票失败 #用户3买票失败
总结:
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 1 队列和管道都是将数据存放于内存中 2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列(推荐使用)
""" 存取数据 存是为了更好的取 千方百计的存、简单快捷的取 """ from multiprocessing import Queue # 创建一个队列 q = Queue(5) # 括号内可以传数字 标示生成的队列最大可以同时存放的数据量 # 往队列中存数据 q.put(111) q.put(222) q.put(333) # print(q.full()) # 判断当前队列是否满了 # print(q.empty()) # 判断当前队列是否空了 q.put(444) q.put(555) # q.put(666) # 当队列数据放满了之后 如果还有数据要放程序会阻塞 直到有位置让出来 不会报错 # 去队列中取数据 v1 = q.get() v2 = q.get() v3 = q.get() v4 = q.get() v5 = q.get() # v6 = q.get() # 队列中如果已经没有数据的话 get方法会原地阻塞 # V6 = q.get_nowait() # 没有数据直接报错queue.Empty # v6 = q.get(timeout=3) # 没有数据之后原地等待三秒之后再报错 queue.Empty print(v1, v2, v3, v4, v5) try: v6 = q.get(timeout=3) print(v6) except Exception as e: print('一滴都没有了!') """ q.full() q.empty() q.get_nowait() 在多进程的情况下是不精确(有可能在判断队列有没有满的一瞬间 就又取出了一个 结果判断True 所以说不精确) """
IPC机制
""" 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() #我是23号技师 很高兴为您服务
生产者消费者模型 JoinableQueue
""" JoinableQueue 每当你往该队列中存入数据的时候 内部会有一个计数器+1 没当你调用task_done的时候 计数器-1 q.join() 当计数器为0的时候 才往后运行 """ from multiprocessing import Process,JoinableQueue import time import random #生产者 def producer(name,food,q): for i in range(3): 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() time.sleep(random.randint(1,3)) print('%s吃了%s'%(name,food)) q.task_done() # 告诉队列你已经从里面取出了一个数据并且处理完毕了 if __name__ == '__main__': 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 # 只要q.join(主进程)执行完毕 说明消费者已经处理完数据了 消费者就没有存在的必要了 c2.daemon = True # 只要q.join(主进程)执行完毕 说明消费者已经处理完数据了 消费者就没有存在的必要了 c1.start() c2.start() p1.join() # 等待 生产者 生产完 p2.join() # 等待 生产者 生产完 q.join() #q.join() 当计数器为0的时候 才往后运行 # 等待队列中所有的数据被取完再执行往下执行代码 说明消费者已经处理完数据了 消费者就没有存在的必要了

浙公网安备 33010602011771号