什么是进程?
额,进程呢就是你运行一个程序,这时他就启动了一个进程。。。(真的没啥好说的了,)
不同进程的关系
每一个进程都有自己的资源,并且是不共享的,就像你家的鸡不会送给邻居家吃(特例除外。。。)
子进程的资源是将主进程的资源进行了拷贝
,虽然你操作的变量名字一样,但是眼前人已非彼时人,最后输出,唯有失望!
如何使用多进程
python使用多进程一共有三个包可以用,因为multiprocessing这个包兼容性好,所以我们用它来做介绍。
要注意,在Windows环境下要将主进程代码写在if __name__=='__main__':下面
,
因为操作系统问题,Windows系统会把后面的代码也拷贝,导致形成无限递归,会一直报错,但是写在if __name__=='__main__':下面
之后,系统会自动判断。
多进程开始
我们可以试使用multiprocessing包中的Process方法创建多进程
from multiprocessing import Process
def process1(x):
print(x)
for i in range(1,4):
print(i,'*'*15,1)
def process2(x):
print(x)
for i in range(5,8):
print(i,'*'*15,2)
if __name__ == '__main__':
first = Process(target = process1,args=(1,))
second = Process(target = process2,args=(2,))
first.start()
second.start()
锵锵锵,我们在主进程里面创建了两个子进程哦。
在这里我对代码做一个解释:
- Process表示创建一个进程
- target表示进程要调用的函数,注意不要写小括号,写了的话就成了实现函数了,不是参数了
- args表示传入函数的参数,当参数只有一个的时候要在最后面写上逗号,我这里只是为了体现有这个功能才写的参数x
- start表示进程开始执行,但各个进程的执行顺序不是按代码执行的,是cpu分配的,可能每次和每次都不同
代码加上个join
from multiprocessing import Process
def process1(x):
print(x)
for i in range(1,4):
print(i,'*'*15,1)
def process2(x):
print(x)
for i in range(5,8):
print(i,'*'*15,2)
if __name__ == '__main__':
first = Process(target = process1,args=(1,))
second = Process(target = process2,args=(2,))
first.start()
first.join
second.start()
这个代码不管你执行多少次,都是先执行process1,然后执行process2。这是join函数的作用。
我们使用进程.join(),就表示告诉主进程,等这个子进程执行完了再干别的事
join、start、run都是什么意思
start方法中包含run方法,其实写run和start的效果都是差不多的。
join的意思是当前进程执行完再执行别的进程,通常用于进程同步。
多进程不能同步全局变量
我们重新写两个进程,一个修改全局变量,一个输出全局变量,看看是什么效果
from multiprocessing import Process
x = 10
def process1():
global x
for i in range(1,4):
x += 1
print(x,'process1')
def process2():
global x
print(x,'process2') # 看看能不能得到process1处理后的x
x -=1
print(x,'process2')
if __name__ == '__main__':
first = Process(target = process1)
second = Process(target = process2)
first.start()
first.join()
second.start()
print(x,'main') # 判断全局变量有没有被改变
这是因为,每个进程都是将主进程的资源copy了一份,对本进程的变量进行修改,不是对主进程的变量进行修改!
如何使用多进程同步全局变量
可以试用管道或队列的方法,在这里,推荐使用队列的方法,因为队列方便操作。
队列是一种先进先出的数据结构,python的队列可以放置任意数据类型,常用函数如下
函数 | 描述 |
---|---|
q.empty() | 判断队列是否为空 |
q.put() | 在队列末尾添加一个元素 |
q.get() | 取出队列第一个元素 |
我们在传参数的时候,要将队列作为参数一起传进去,修改后的代码如下:
from multiprocessing import Process,Queue
def process1(q):
for i in range(3):
q.put(i)
def process2(q):
while not q.empty():
print(q.get())
if __name__ == '__main__':
q = Queue(5) # 表示队列最多放五个元素
first = Process(target = process1,args = (q,))
second = Process(target = process2,args = (q,))
first.start()
first.join()
second.start()
我们惊奇的发现,全局变量同步了诶hiahiahia,这是因为队列是写在内存上的哦
进程池简介
当我们想批量执行一个函数,又不想去用列表创建的时候,或者是异步执行的的时候,我们就可以使用进程池。
进程池进程的最大数量不能超过你cpu内核,一般来说4个进程就可以了,你可以使用下面的代码看看你是多少核的cpu
import os
print("本机为",os.cpu_count(),"核 CPU") # 本机为4核
流量池的使用步骤如下:
- 创建流量池
- apply_async添加异步函数或者apply添加同步函数
- 调用close()方法来停止添加函数
- join()开始执行调用流量池
使用进程池同步调用函数
from multiprocessing import Pool
import time
def func(msg):
print( "msg:", msg)
time.sleep(0.1)
return msg
if __name__ == "__main__":
pool = Pool(processes = 3)
res_l=[]
for i in range(10):
msg = "hello %d" %(i)
res = pool.apply(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个
print("==============================>")
pool.close()
pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print(res_l) #看到的就是最终的结果组成的列表
for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法
print(i)
需要强调的是:此操作并不会在所有池工作进程中并执行func函数。
如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()
使用进程池异步调用函数
from multiprocessing import Pool
import time
def func(msg):
print( "msg:", msg)
return msg
if __name__ == "__main__":
pool = Pool(processes = 3)
res_l=[]
for i in range(10):
msg = "hello %d" %(i)
res = pool.apply_async(func, (msg, ))
res_l.append(res)
print("==============================>")
pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果
for i in res_l:
print(i.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
这个程序就代表,同一时间,有四个进程在跑这个函数
如何用进程池使用队列
使用流量池同步数据,也是使用队列,但是这里的队列来源不同
from multiprocessing import Manager
q = Manager.Queen(5)
其他操作就没啥了,把q也作为传到函数里就好了
如何获取进程编号
os库提供了获取进程编号的相关方法。
函数 | 描述 |
---|---|
os.getpid() | 获取当前进程的编号 |
os.getppid() | 获取父进程的编号 |
特别要注意的地方
要根据情况慎用join,因为稍不留意,你的多进程(线程)程序效果还不如单进程(线程)。
因为如果你总是等一个进程执行完再去执行另外一个进程,可能执行时间比单进程还要长,建议大家使用流量池异步执行或者不写join