python 33 多进程(一)
目录
多进程
1. 进程创建的两种方式 multiprocessing
# 第一种方式
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(2)
print(f'{name} is gone')
if __name__ == '__main__': # 在Windows环境下,必须在main下开启子进程
p = Process(target=task, args=('meet',)) # args元组形式 创建一个进程对象
p.start()
print("===主进程")
"""
结果:
===主进程
meet is running
meet is done
"""
# p.start():开启子进程的命令,通知操作系统在内存中开辟一个进程空间,将主进程的数据copy加载到子进程空间中,然后操作系统调用cpu去执行子进程。 开销较大,因此会先执行主进程的代码。
# 第二种方式
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super().__init__() # 重构父类的方法
self.name = name
def run(self): # 必须写run()
print(f'{self.name} is running')
time.sleep(2)
print(f"{self.name} is gone")
if __name__ == '__main__':
p = MyProcess('meet') # 默认执行run()
p.start() # 开启子进程的命令
print("===主进程")
2. 进程pid (process id)
进程在内存中的唯一标识。
终端命令: tasklist # 查看计算机当前所有进程的pid
终端命令: tasklist|findstr pycharm # 查看pycharm的pid
代码 :
import os
print(os.getpid()) # 查看当前进程的pid
print(os.getppid()) # 查看父进程的pid
3. 验证进程之间的空间隔离
from multiprocessing import Process
lst = [1, 2]
def task():
lst.append(3)
print(f'子进程的:{lst}')
if __name__ == '__main__':
p = Process(target=task)
p.start()
print(f'主进程的:{lst}')
"""
主进程的:[1, 2]
子进程的:[1, 2, 3]
"""
# 主进程与子进程之间存在空间隔离。
4. 进程对象join方法
join :让主进程等待子进程结束之后,再执行主进程。(让主进程阻塞)
from multiprocessing import Process
import time
def task(sec):
print('is running')
time.sleep(sec)
print('is gone')
if __name__ == '__main__':
start_time = time.time()
p1 = Process(target=task, args=(1,))
p2 = Process(target=task, args=(2,))
p3 = Process(target=task, args=(3,))
p1.start()
p2.start()
p3.start()
# 同时开启
p1.join()
print(f'p1时间:{time.time()-start_time}') # 1s多
p2.join()
print(f'p2时间:{time.time()-start_time}') # 2s多
p3.join()
print(f'p3时间:{time.time()-start_time}') # 3s多
from multiprocessing import Process
import time
def task(sec):
print('is running')
time.sleep(sec)
print('is gone')
if __name__ == '__main__':
start_time = time.time()
p1 = Process(target=task, args=(3,))
p2 = Process(target=task, args=(2,))
p3 = Process(target=task, args=(1,)) # 改变时间
p1.start()
p2.start()
p3.start()
# 同时开启
p1.join()
print(f'p1时间:{time.time()-start_time}') # 3s多 #阻塞
p2.join()
print(f'p2时间:{time.time()-start_time}') # 3s多
p3.join()
print(f'p3时间:{time.time()-start_time}') # 3s多
# 面试题:将下面代码优化
from multiprocessing import Process
import time
def task(sec):
print('is running')
time.sleep(sec)
print('is gone')
if __name__ == '__main__':
start_time = time.time()
p1 = Process(target=task, args=(1,))
p2 = Process(target=task, args=(2,))
p3 = Process(target=task, args=(3,))
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print(f'运行时间:{time.time()-start_time}') # 3s多
# 上面代码优化:
lst = []
for i in range(1,4):
p = Process(target=task, args=(i,))
lst.append(p)
p.start()
for i in lst:
i.join()
print(f'运行时间:{time.time()-start_time}')
# 错误的优化:
for i in range(1,4)
p = Process(target=task, args=(i,))
p.start()
p.join()
print(f'运行时间:{time.time()-start_time}')
5. 进程对象其他属性
p.terminate() # 结束子进程
p.is_alive() # 判断子进程是否活着 True/False
p = Process(target=task, args=('meet',), age=18) # 给子进程对象添加一个age属性。
print(p.age) # 18
6. 僵尸进程与孤儿进程
6.1 僵尸进程
实际中,主进程只有等待子进程结束后,主进程才结束。
基于Unix环境(linux,macOS):
主进程时刻监测子进程的运行状态,当子进程结束后,一段时间内,才会将子进程回收。
为什么不马上回收?
- 主进程与子进程是异步关系,主进程无法马上会获取子进程是什么时候结束;
- 如果子进程结束之后马上在内存中释放资源,主进程就没有办法监测子进程的状态。
Unix针对上面的问题,提供了一种机制:
所有的子进程结束后,立马释放掉文件的操作链接、内存等的大部分数据,但是会保留一些内容:进程号、结束时间、运行状态,等待子进程监测、回收。
僵尸进程:子进程在结束后,主进程并没有调用wait或waitpid获取子进程的状态信息,子进程仍然保留了一些描述内容,这种进程被称为僵尸进程。
危害:如果父进程不对僵尸进程进行回收(wait/waitpid),产生大量的僵尸进程,这样会占用内存,占用进程的pid号。
6.2 孤儿进程
父进程由于某种原因结束,但子进程还在运行中,则称这些子进程为孤儿进程。
回收机制:Unix中孤儿进程会被 init 进程回收,init 变成父进程。
僵尸进程解决方法:
直接杀死父进程。将所有僵尸进程变成孤儿进程,由 init进程回收。
7. 守护进程
# 子进程守护着主进程,只要主进程结束,子进程也跟着结束。
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(2)
print(f'{name} is gone')
if __name__ == '__main__':
p = Process(target=task, args=('meet',)) # 创建一个进程对象
p.daemon = True # 放在start前,否则会报错。
p.start()
time.sleep(1)
print("===主进程")
"""
meet is running
===主进程
"""