进程
目录
-
代码创建进程
-
join方法
-
进程间数据默认隔离
-
进程对象属性和方法
-
僵尸进程与孤儿进程
-
守护进程
-
互斥锁
内容
代码创建进程
创建进程的方式
- 鼠标双击桌面一个应用图标
- 代码创建
创建进程的方式
在内存中申请一块内存空间用于运行相应的程序代码
代码创建进程的两种方式
第一种:函数
from multiprocessing import Process import time def task(name): print(f'{name} is running') time.sleep(3) print(f'{name} is over') if __name__ == '__main__': p = Process(target=task,args=('zhou',)) # 创建一个进程对象 p.start() # 告诉操作系统创建一个新的进程 print('主进程')
打印结果为:
主进程 zhou is running zhou is over
第二种:面向对象
from multiprocessing import Process import time class MyProcess(Process): def __init__(self,username): self.username = username super().__init__() def run(self): print('hello',self.username) time.sleep(3) print('hi',self.username) if __name__ == '__main__': p = MyProcess('zhou') p.start() print('主进程')
打印结果为:
主进程
hello zhou
hi zhou
进程实现并发
服务端:
import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: # 链接循环 sock,add = server.accept() while True: # 通信循环 data = sock.recv(1024) # 不考虑粘包问题 print(data.decode('utf8')) sock.send(data.upper()) # 客户端发一个消息就立马回复一个消息的大写
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello') data = client.recv(1024) print(data.decode('utf8'))
多个客户端同时运行只能运行一个,服务端只能与一个进行交互
方法:将服务客户端的代码封装成函数(通信代码),并创建进程
服务端:
import socket from multiprocessing import Process server = socket.socket() server.bind(('127.0.0.1',8989)) server.listen(5) def task(sock): while True: # 通信循环 data = sock.recv(1024) # 不考虑粘包问题 print(data.decode('utf8')) sock.send(data.upper()) # 客户端发一个消息就立马回复一个消息的大写 if __name__ == '__main__': while True: # 链接循环 sock,add = server.accept() p = Process(target=task,args=(sock,)) p.start()
join方法
from multiprocessing import Process import time def task(name): print(f'{name} is running') time.sleep(3) print(f'{name} is over') if __name__ == '__main__': p = Process(target=task,args=('zhou',)) p.start() print('主进程')
基于上述代码产生需求
需求:想让p.start()之后的代码,等待子进程全部运行结束之后再打印
这里就用到了join方法:
from multiprocessing import Process import time def task(name): print(f'{name} is running') time.sleep(3) print(f'{name} is over') if __name__ == '__main__': p = Process(target=task,args=('zhou',)) p.start() p.join() # 等待子进程运行结束之后再往下执行 print('主进程')
如果子进程的睡眠时间是 sleep(n),并且有三个进程:
from multiprocessing import Process import time def task(name,n): print(f'{name} is running') time.sleep(n) print(f'{name} is over') if __name__ == '__main__': p1 = Process(target=task, args=('zhou', 1)) p2 = Process(target=task, args=('zhou', 2)) p3 = Process(target=task, args=('zhou', 3)) start_time = time.time() p1.start() p2.start() p3.start() p1.join() p2.join() p3.join() end_time = time.time() - start_time print('主进程',f'总耗时:{end_time}')
请问总耗时大概是几秒?
结果:
zhou is running chen is running jin is running zhou is over chen is over jin is over 主进程 总耗时:3.1336801052093506
把上述代码修改一下:
from multiprocessing import Process import time def task(name,n): print(f'{name} is running') time.sleep(n) print(f'{name} is over') if __name__ == '__main__': p1 = Process(target=task, args=('zhou', 1)) p2 = Process(target=task, args=('chen', 2)) p3 = Process(target=task, args=('jin', 3)) start_time = time.time() p1.start() p1.join() p2.start() p2.join() p3.start() p3.join() end_time = time.time() - start_time print('主进程',f'总耗时:{end_time}')
那这次又是多少秒呢?
结果:
zhou is running zhou is over chen is running chen is over jin is running jin is over 主进程 总耗时:6.378488302230835
进程间数据默认隔离
内存可以看成是有很多个小隔间组成的,彼此不干扰
from multiprocessing import Process money = 999 def task(): global money # 局部修改全局不可变类型 money = 666 if __name__ == '__main__': p = Process(target=task) p.start() p.join() print(money)
打印结果是:999
而子进程的money是多少呢?
from multiprocessing import Process money = 999 def task(): global money # 局部修改全局不可变类型 money = 666 print('子进程',money) if __name__ == '__main__': p = Process(target=task) p.start() p.join() print(money)
结果为:
子进程 666
999
进程对象属性和方法
查看进程号
- windows终端: tasklist 进程号:PID
- mac终端:ps -ef 进程号:PID
代码查看进程号
获取进程号的用处之一:通过代码的方式管理进程
- current_process函数
from multiprocessing import Process,current_process print(current_process().pid)
这里会拿到一个进程号,加上time模块就可以在拿到进程号之前在终端查看到该进程号
- os 模块
import os def task(): print('子进程号:',os.getpid()) print('父进程号:',os.getppid()) if __name__ == '__main__': print('主进程号:',os.getpid()) print('主主进程:',os.getppid()) p = Process(target=task) p.start()
os.getpid() 获取当前进程的进程号
os.getppid() 获取当前进程的父进程号
杀死进程号
-
命令行:
windows: taskkill关键字 ; mac/linux: kill关键字
- 代码: terminate()
from multiprocessing import Process,current_process import os def task(): print('子进程号:',os.getpid()) print('父进程号:',os.getppid()) if __name__ == '__main__': p = Process(target=task) p.start() p.terminate() print('主进程')
结果:主进程
判断子进程是否存活
is_alive()
import os def task(): print('子进程号:',os.getpid()) print('父进程号:',os.getppid()) if __name__ == '__main__': p = Process(target=task) p.start() p.terminate() print(p.is_alive()) print('主进程')
结果为:
True
主进程
布尔值为True的原因:terminate告诉操作系统杀死子进程需要一点时间
import os import time def task(): print('子进程号:',os.getpid()) print('父进程号:',os.getppid()) if __name__ == '__main__': p = Process(target=task) p.start() p.terminate() time.sleep(0.1) print(p.is_alive()) print('主进程')
结果为:
False
主进程
僵尸进程与孤儿进程
僵尸进程
所有的子进程在运行结束之后都会变成僵尸进程(死了但是没有死透)
还保留着pid和一些运行过程的中的记录便于主进程查看(短时间保存)
这些信息会被主进程回收(僵尸彻底死了)
孤儿进程
子进程存活者,父进程意外死亡
子进程会被操作系统自动接管
守护进程
守护:死活全部参考守护的对象,对象死立刻死
from multiprocessing import Process import time def task(name): print(f'{name}还活着') time.sleep(3) print(f'{name}死了') if __name__ == '__main__': p = Process(target=task,args=('zhou',)) p.daemon = True # 将子进程设置为守护进程:主进程结束,子进程立刻结束 p.start() print('chen')
结果为:chen
如果给主进程加一秒
from multiprocessing import Process import time def task(name): print(f'{name}还活着') time.sleep(3) print(f'{name}死了') if __name__ == '__main__': p = Process(target=task,args=('zhou',)) p.daemon = True # 将子进程设置为守护进程:主进程结束,子进程立刻结束 p.start() time.sleep(1) print('chen')
结果为:
zhou还活着
chen
使用场景:只要自己结束了,守护的程序也直接结束,不继续占用资源了
模拟抢票程序
import json from multiprocessing import Process import time import random # 查票 def check(name): with open(r'ticket_data.json','r',encoding='utf8') as f: data = json.load(f) print(f'{name}查询当前余票:%s'%data.get('ticket_num')) # 买票 def buy(name): with open(r'ticket_data.json','r',encoding='utf8') as f: data = json.load(f) time.sleep(random.randint(1,3)) # 判断是否还有余票 if data.get('ticket_num') > 0: data['ticket_num'] -= 1 with open(r'ticket_data.json','w',encoding='utf8') as f: json.dump(data,f) print(f'{name}抢票成功') else: print(f'{name}抢票失败,没有余票了') def run(name): check(name) buy(name) if __name__ == '__main__': for i in range(10): p = Process(target=run,args=('用户:%s'%i,)) p.start()
结果为:
用户:0查询当前余票:4 用户:1查询当前余票:4 用户:2查询当前余票:4 用户:3查询当前余票:4 用户:4查询当前余票:4 用户:5查询当前余票:4 用户:6查询当前余票:4 用户:8查询当前余票:4 用户:7查询当前余票:4 用户:9查询当前余票:4 用户:8抢票成功 用户:1抢票成功 用户:2抢票成功 用户:3抢票成功 用户:4抢票成功 用户:7抢票成功 用户:0抢票成功 用户:6抢票成功用户:5抢票成功 用户:9抢票成功
互斥锁
刚才的抢票程序可以看出:当多个进程操作同一份数据的时候会造成数据的错乱
这个时候需要加锁处理(互斥锁)
将并发变成串行,虽然牺牲了效率但是保证了数据的安全
互斥锁在主进程中使用
互斥锁不能轻易使用,容易造成死锁现象
互斥锁只在处理数据的部分加锁,不能什么地方都加,严重影响程序的效率
正确的抢票程序:
import json from multiprocessing import Process,Lock import time import random # 查票 def check(name): with open(r'ticket_data.json','r',encoding='utf8') as f: data = json.load(f) print(f'{name}查询当前余票:%s'%data.get('ticket_num')) # 买票 def buy(name): with open(r'ticket_data.json','r',encoding='utf8') as f: data = json.load(f) time.sleep(random.randint(1,3)) # 判断是否还有余票 if data.get('ticket_num') > 0: data['ticket_num'] -= 1 with open(r'ticket_data.json','w',encoding='utf8') as f: json.dump(data,f) print(f'{name}抢票成功') else: print(f'{name}抢票失败,没有余票了') def run(name,mutex): check(name) # 只需要把买票环节变成串行即可 mutex.acquire() # 抢锁 buy(name) mutex.release() # 放锁 if __name__ == '__main__': mutex = Lock() for i in range(1,10): p = Process(target=run,args=('用户:%s'%i,mutex)) p.start()
锁的相关知识:
行锁:针对行数据加锁,同一时间只能一个人操作
表锁:针对表数据加锁,同一时间只能一个人操作
锁的应用范围很广,但是核心都是为了保证数据的安全