在学习多进程之前,我们先来复习几个基本的知识点:
1.什么是进程:进程指的是一个程序的运行过程,或者说一个正在执行的程序,所以说进程是一种虚拟的概念,该虚拟概念起源于操作系统
2.操作系统发展史:
(1)多道技术:1.空间上的复用即多个进程加载到同一片内存空间 是为时间上的复用做准备
ps:进程之间的内存空间是互相隔离的
2.时间上的复用:多个任务复用/共享CPU的时间 即cpu在多个任务之间来回切换
1.一个任务占用cpu时间过长会被操作系统夺回执行权限:只是为了保证一个并发的效果,并不会 提高程序的执行效率
2.一个任务遇到I/O操作会被操作系统强行夺回执行权限:为了实现并发的效果,可以提高效率
(2)大前提:一个CPU同一时刻只能执行一个任务
串行:一个任务完完整整运行完毕才能执行下一个任务
并发:多个任务看起来是同时运行的,单核就能实现并发(并发=切换+保存状态)
并行:同一时刻多个任务是真正意义上的同时运行,只有多核才能实现并行
说在前面:无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:
1.在linux系统中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,两者有相同的存储映像,同样的环境字符串和同样的打开文件。
2.在windows中该系统调用的是CreatProcess,CreatProcess即处理进程的创建,也负责把程序装进新进程中
关于创建的子进程,linux和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程在其地址空间中的额修改都不会影响到另外一个进程
2.不同的是,在linux中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存的,但是对于windows系统来说,子进程与父进程的地址空间是不同的
3.一个进程有三种状态:
开启子进程的方式一:(采用系统自带的Process类)
from multiprocessing import Process
import time
def task(name):
print("%s is running " %name)
time.sleep(3)
print("%s is done " %name)
#在windows系统上开启子进程的操作必须放到if __name__=="__main__":的子代码中
if __name__=="__main__":
p=Process(target=task,args=("egon",))
p.start() #只是向操作系统发起了一条开启子进程的信号,操作系统创建子进程需要一段时间,所以会直接运行print("这是主进程")这段代码
p.join()
print("这是主进程")
开启子进程的方式二:(自定义一个创建进程的类)
from multiprocessing import Process
import time
class Myprocess(Process):
def __init__(self, name):
self.name = name
def run(self):
print("%s is running" % self.name)
time.sleep(3)
print("%s is done" % self.name)
if __name__ == "__main__":
p = Myprocess("egon")
p.start()
print("住")
二.join()方法(让主进程在原地等待此时主进程为阻塞状态,等待子进程运行完毕)
from multiprocessing import Process
import time
def task(name):
print("%s is running" %name)
time.sleep(3)
print("%s is done" % name)
if __name__=="__main__":
p1=Process(target=task,args=("子进程1",))
p2=Process(target=task,args=("子进程2",))
start=time.time()
p1.start()
p1.join()
print(str(time.time() - start))
p2.start()
print("住")
执行结果为:
三.进程之间内存空间互相隔离(子进程地址空间的数据修改不会影响到主进程)
from multiprocessing import Process
n=100
def task():
global n
n=0
print(n) #0
if __name__=="__main__":
p=Process(target=task)
p.start()
print(n) #100
四.进程对象其他相关属性和方法
1.进程pid:每一个进程在操作系统中都有一个唯一的id号,称之为pid
from multiprocessing import Process,current_process
import time
def task(name):
print("%s is running" %current_process().pid)
time.sleep(3)
print("%s is done" %current_process().pid)
if __name__ == '__main__':
p=Process(target=task,args=("zs",))
p.start()
查看进程对象的pid有两种方法:1.current_process().pid 2.os.getpid()
查看父进程的pid的方法os.getppid()
from multiprocessing import Process,current_process
import time,os
n=55555
def task(name):
print("%s is running 爹是%s" %(os.getpid(),os.getppid()))
time.sleep(3)
print("%s is done 爹是%s" % (os.getpid(), os.getppid()))
if __name__ == '__main__':
p=Process(target=task,args=("zs",))
p.start()
print("主进程id是%s:" % os.getpid())
p.terminate():操作系统杀死子进程
p.is_alive():判断子进程是否活着
四.僵尸进程与孤儿进程(了解)
僵尸进程:所有的子进程执行完任务之后,都会释放内存资源(pid号不会被回收仍然占用一点内存资源,不过这一点内存资源是微乎其微的 基本忽略不计),成为僵尸进程,等待主进程回收子进程的pid进程编号,主进程回收进程编号有两种方式,一是主进程主动回收p.join(),还有一种是主进程会定期去回收子进程的pid号
孤儿进程:当主进程执行完任务,子进程还没执行完,这时的子进程称为孤儿进程,子进程执行完任务后会变成僵尸进程,在linux系统中,所有进程的父进程为init,其进程编号pid=0,当父进程执行完后,子进程的pid号有init来回收
五.守护进程
守护进程:守护进程本质是一个“子进程”,该守护进程的生命周期小于等于被守护的进程的生命周期
from multiprocessing import Process,current_process
import time,os
def task(name):
print("%s is running " %name)
time.sleep(3)
print("%s is done " %name)
if __name__ == '__main__':
p=Process(target=task,args=("zs",))
p.daemon=True
p.start()
time.sleep(2)
print("主进程正在死")
如果子进程为守护进程,子进程随着主进程的结束而结束
六.互斥锁
下面是一个抢票的例子来说明互斥锁:
import json
import time,random
from multiprocessing import Process,Lock
def search(name):
with open('db.json','rt',encoding='utf-8') as f:
dic=json.load(f)
time.sleep(1)
print('%s 查看到余票为 %s' %(name,dic['count']))
def get(name):
with open('db.json','rt',encoding='utf-8') as f:
dic=json.load(f)
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(random.randint(1,3))
with open('db.json','wt',encoding='utf-8') as f:
json.dump(dic,f)
print('%s 购票成功' %name)
else:
print('%s 查看到没有票了' %name)
def task(name,mutex):
search(name) #并发
mutex.acquire()
get(name) #串行
mutex.release()
# with mutex:
# get(name)
if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p=Process(target=task,args=('路人%s' %i,mutex))
p.start()
# p.join() # join只能将进程的任务整体变成串行
总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理