python多进程技术

进程的概念

可执行的代码就叫程序。
正在运行着的代码+需要的一些资源就是进程。

例子:QQ没打开的时候是程序,打开了之后是进程。

fork

fork()调用一次,返回两次。从返回处开始,父子进程的代码就是一样的了。
意思大概是这样的:

import os

ret = os.fork()
if ret == 0:
    print('我是子进程')
else:
    print('我是父进程')

ret = os.fork() 这句往下,父进程和子进程都会往下执行:

if ret == 0:
    print('我是子进程')
else:
    print('我是父进程')

fork()0返回给子进程,将子进程的ID返回给父进程。
获取自己进程的ID用os.getpid(),获取父进程的ID用os.getppid()
fork()的话,父进程不会等待子进程结束。(也就是说,主进程可以在子进程结束之前结束)

进程的执行过程
操作系统在面对多进程的时候,会根据自身的调度算法来进行执行。所以你不知道每个进程执行了多少代码后就被CPU给踢出来了。这都是操作系统自己决定的,程序员管不了。

进程的资源是独立的,所以程序中的全局变量在多进程中是不共享的。
什么意思呢?

import os
import time
nums = 1
ret = os.fork()
if not ret:
        print('zijincheng')
        nums += 1
else:
        time.sleep(1)
        print('fujincheng')

print(nums)

输出结果:

[xm@vth 桌面]$ python multi.py
zijincheng
2
fujincheng
1

我们在子进程中对nums进行了修改,并让主线程睡了1秒来保证先执行子进程,但是我们可以看到,子进程修改了nums的值对主进程中的nums的值并没有任何影响。
如果想共享咋办???要进程间通信技术。
fork炸弹

while True:
    os.fork()

multiprocessing-Process

很遗憾,fork()windows上不能用。所以我们用fork写不出跨平台的程序。
但是我们的python如此强大,它肯定能写出来跨平台的多进程程序。
multiprocessing就可以解决这个问题。
在这里插入图片描述
注意,在windows平台上用Process写多进程时,需要在main函数中执行(Linux中就无所谓了):

import os
from multiprocessing import Process

def pr():
    print('aaa')
if __name__ == '__main__':
    p1 = Process(target=pr)
    p1.start()
    p2 = Process(target=pr)
    p2.start()

不这么写会报错。
Process写的多进程程序,主进程会等待子进程结束后再结束(注意,是结束,而不是执行哦)。意思就是,如果启动子进程是主线程的最后一条语句的话,我们不用care这个问题,因为主进程没有语句了,只需要等待子进程结束然后自己也结束即可。
如果开启子进程后主进程还想执行点东西,是可以的。因为主进程和子进程一起抢占CPU,主进程并不会等子进程结束之后再执行自己的代码。
如果我们现在有一个需求:主进程需要等待子进程执行完毕后再执行一些语句,那怎么办呢? 用join() join()起的作用是阻塞主进程。

import os
from multiprocessing import Process
import time
def pr(i):
    print('aaa%d'%i)
    time.sleep(3)
if __name__ == '__main__':
    p1 = Process(target=pr,args=(1,))
    p1.start()
    p1.join(1) # 也可以给join设置一个timeout
    print('hhh')

join()解除阻塞状态有两个可选条件:
1、timeout超时
2、子进程运行结束
比如上面这样一个程序,子进程执行1秒后,主进程就可以输出hhh了。

terminate() # 直接杀死子进程。
示例:

import os
from multiprocessing import Process
import time
def pr(i): # 子进程需要执行的代码
    print('aaa%d'%i)
    time.sleep(3)
if __name__ == '__main__':
    p1 = Process(target=pr,args=(1,))
    p1.start()
    p1.join(1) # 也可以给join设置一个timeout
    print('hhh')
    p1.terminate() # 直接强制杀掉p1这个进程

multiprocessing-继承Process类

也可以通过继承Process类来创建进程:
只需要写一个继承自Process的类,然后重写类的run()方法即可。

import os
from multiprocessing import Process
import time

class Demo(Process): # 继承自Process
    def run(self):
        print('wocao')

if __name__ == '__main__':
    d = Demo() 
    d.start() # 开启进程

进程池Pool

池的作用一般都是缓冲。(蓄水池、备胎池2333333333)

from multiprocessing import Pool
import time
def r(i):
    print('hhh%d'%i)
    time.sleep(0.5)

if __name__ == '__main__':
    po = Pool(3)  # 定义了一个最大进程数为3的进程池
    for i in range(10):
        po.apply_async(r,(i,)) # r 为要调用的目标,后面可用一个元组给出目标的参数
        # 循环10次的目的是把任务加入到进程池中(for是一下子就执行完了,跟进程池没关系)
        # 我们会发现输出是3个3个往外蹦,因为进程池有3个进程
    po.close() # 关闭进程池 关闭后不再接收新的请求
    po.join()

在这里插入图片描述
注意,用进程池写的程序,主线程不会等待子进程结束后再结束。而且主进程结束之后子进程也会跟着结束,并不像fork那样,主进程结束之后子进程还能做一些事情。在Pool中,主进程死了,子进程就没了。所以如果要想让主进程等待子进程,需要加上join()
而且,在po.apply_async(r,(i,))时,进程池中的子进程就开始执行了,并不是非要等任务全部添加完成再执行。
代码:

from multiprocessing import Pool
import time
def r(i):
    print('hhh%d'%i)
    time.sleep(0.5)

if __name__ == '__main__':
    po = Pool(3)  # 定义了一个最大进程数为3的进程池
    for i in range(100000):
        po.apply_async(r,(i,)) # r 为要调用的目标,后面可用一个元组给出目标的参数
        print(1)
        # 我们会发现输出是3个3个往外蹦,因为进程池有3个进程
    print('wocao')
    po.close() # 关闭进程池 关闭后不再接收新的请求

    po.join()

证据:
在这里插入图片描述
往进程池中添加的任务的数量是不限制的。
进程池就相当于开了个公司,然后招了3个小弟给你干活。来了活就交给小弟干,别管多少活都要交给小弟干。要合理的设置小弟数量(因为切换时耗费大量资源)。
po.apply_async(r,(i,))这种方式是非阻塞方式。
接下来我们看看阻塞apply的怎么写:

from multiprocessing import Pool
import time
def r(i):
    print('hhh%d'%i)
    time.sleep(0.5)

if __name__ == '__main__':
    po = Pool(3)
    for i in range(100000):
        po.apply(r,(i,))
    print('wocao')
    po.close()

    po.join()

其实就是把apply_async改成了apply
apply会一个一个的往外蹦,并不会3个3个的往外蹦。

进程间通信Queue

我们创造出来进程,就是想让很多进程协作来完成复杂的任务,所以说进程间必须要可以通信。那怎么实现进程间通信呢?用队列Queue
既然我们是用Queue来实现进程间通信的,那么我们这就来先好好学一下Queue
我们先来了解一下Queue的两个方法:getput

from multiprocessing import Queue

q = Queue(3)# 创建一个最大容量是3的队列
# q = Queue() # 创建一个无限容量的队列
# q.put(1)
# q.put(1)
# q.put(1)
# q.put(2) # 这句会阻塞。 因为队列满了,放不进去。

q.get() # 这句会阻塞。因为队列空了,取不出来。

然后再来看一下返回队列实际长度的qsize方法:

from multiprocessing import Queue

q = Queue(3) # 创建一个最大容量是3的队列
q.put(2)
print(q.qsize()) # 返回队列的实际长度

然后再来看一下返回队列是空还是满的方法emptyfull

from multiprocessing import Queue

q = Queue(3)
q.put(2)
print(q.empty()) # 队列为空则返回True
print(q.full()) # 队列满了返回False

所以为了避免程序处于阻塞状态,我们在使用getput方法之前需要判空和判满.
当然,我们也可以配合另外两个方法put_nowait()get_nowait(),用异常处理来解决这个问题:

q.put_nowait(obj)
q.get_nowait()
这样的话异常时不会阻塞,只会抛出异常

不能有两个进程同时get,这样会造成时序错误。
来看一个实例:

from multiprocessing import Queue,Process
import os,time,random


def write(q):
    for value in ['A','B','C']:
        print('put %s to queue' % value)
        q.put(value)
        time.sleep(random.random())

def read(q):
    while True:
        if not q.empty():
            value = q.get()
            print('get  %s from queue' % value)
            time.sleep(random.random())
        else:
            break

if __name__ == '__main__':

    q = Queue()
    pw = Process(target=write,args=(q,))
    pr = Process(target=read, args=(q,))

    pw.start()
    pw.join()
    pr.start()
    pr.join()

输出:

put A to queue
put B to queue
put C to queue
get  A from queue
get  B from queue
get  C from queue

刚刚我们说的都是Process创造的多线程程序怎么用Queue,那么进程池Pool弄出来的多进程该怎么用Queue呢?
应该用即可:

from multiprocessing import Manager,Pool
q = Manager().Queue()

别的东西都一样。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页