Python并发编程(二):多进程(实战基础篇)

下面是小凰凰的简介,看下吧!
人生态度:珍惜时间,渴望学习,热爱音乐,把握命运,享受生活
学习技能:网络 -> 云计算运维 -> python全栈( 当前正在学习中)
您的点赞、收藏、关注是对博主创作的最大鼓励,在此谢过!
有相关技能问题可以写在下方评论区,我们一起学习,一起进步。
后期会不断更新python全栈学习笔记,秉着质量博文为原则,写好每一篇博文。

文章目录

    • 一、multiprocessing模块介绍
    • 二、Process类的介绍
        • 1、创建进程的类
        • 2、方法介绍
    • 三、 Process类的使用
    • 四、创建并开启子进程的两种方式
        • 1、方法一
        • 2、方法二
    • 五、各个进程的内存空间是隔离的
    • 六、Process对象的各种方法
        • 1、join方法
        • 2、其他方法(了解)
            • (1)terminate与is_alive
            • (2)name与pid
            • (3)os.getpid和os.getppid
    • 七、僵尸进程与孤儿进程、守护进程
        • 1、僵尸进程与孤儿进程(了解)
        • 2、守护进程
    • 八、socket通信实现并发(并不是socketserver)
        • 1、初级版本实现方法
    • 九、进程池
        • 1、创建进程池的类详解
        • 2、进程池的应用
            • (1)同步调用apply
            • (2)异步调用apply_async
        • 3、socket通信实现并发(进程池升级版)
        • 4、异步回调函数

一、multiprocessing模块介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

二、Process类的介绍

1、创建进程的类

Process(...),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

# 参数如下
 group参数未使用,值始终为None
 
 target表示调用对象,即子进程要执行的任务
 
 args表示调用对象的位置参数元组,args=(1,2,'egon',)
 
 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
 
 name为子进程的名称

2、方法介绍

 p.start():启动进程,并调用该子进程中的p.run() 
 
 p.run():进程启动时自动运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  
  
 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了孤儿进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,会导致此资源一直无法被其他进程获得。
 
 p.is_alive():如果p仍然运行,返回True
 
 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,

'p.join只能join住start开启的进程,而不能join住run开启的进程',为什么这么说?,因为p.start可以开启进程,也是因为自动调用了p.run,因此我们也可以直接用p.run开启进程,但是直接用p.run就无法使用join实现进程同步了。

三、 Process类的使用

注意:在windows中Process()必须放到 if name == ‘main’:下

from multiprocessing import Process
# 为什么?
if __name__ == "__main__":
	p1 = Process()
# 先说说fork函数
1. python中的os 模块封装了常见的系统调用,其中就有fork,python程序创建子进程
import os
pid =os.fork()

2. 由于windows没有fork调用,而python又是跨平台的,所以python提供一种跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

3. multiprocessing模块提供了一个Process类来代表一个进程对象。
创建一个Process对象,用start方法来启动
p.start()
p.join()
join方法可以等待子进程结束后在继续往下执行,通常用来进程间的同步。

4. 由于Windows没有fork函数,多处理模块启动一个新的Python进程,新的进程为子进程,模拟fork的感觉,并将父进程所在的文件当成模块导入新的进程中。 如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 这是隐藏对Process()内部调用的原理,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。

5. 有人可能又会问为什么需要把父进程所在的文件导入新进程中呢?注意既然是父子关系,你想下,儿子肯定要用爸爸的一些东西啊,比如爸爸挣的钱。因此子进程直接以模块的形式导入父进程所在的文件。

四、创建并开启子进程的两种方式

1、方法一

import time
import random
from multiprocessing import Process
def piao(name):
    print('%s piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s piao end' %name)


p1=Process(target=piao,args=('haha',)) # 必须加,号,因为args是个元组
p2=Process(target=piao,args=('hehe',))
p3=Process(target=piao,args=('heihei',))
p4=Process(target=piao,args=('xixi',))
# target为一个函数名,那么这个进程的任务就是执行这个函数,args为给这个进程的任务(这里就是这个函数)传的参数,如果是关键字参数传参就是kwargs字典

p1.start()
p2.start()
p3.start()
p4.start()
print('主线程')

2、方法二

import time
import random
from multiprocessing import Process

class Piao(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('%s piaoing' %self.name)

        time.sleep(random.randrange(1,5))
        print('%s piao end' %self.name)

p1=Piao('egon')
p2=Piao('alex')
p3=Piao('wupeiqi')
p4=Piao('yuanhao')

p1.start() # start会自动调用run
p2.start()
p3.start()
p4.start()
print('主线程')

五、各个进程的内存空间是隔离的

from multiprocessing import Process
n=100 # 在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
def work():
    global n
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)
# 执行结果:
主进程内:  100
子进程内:  0
# 注意:先打印主进程可以看出来p.start是异步的,如果要想先打印子进程,就p.join()实现父子进程同步

六、Process对象的各种方法

1、join方法

join:主进程等待子进程结束,父子进程间同步

from multiprocessing import Process
import time
import random

class Piao(Process):
    def __init__(self,name):
        self.name=name
        super().__init__()
    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,3))
        print('%s is piao end' %self.name)


p=Piao('egon')
p.start()
p.join(0.0001) #等待p停止,等0.0001秒就不再等了
print('开始')

有了join,程序不就是串行了吗???

from multiprocessing import Process
import time
import random
def piao(name):
    print('%s is piaoing' %name)
    time.sleep(random.randint(1,3))
    print('%s is piao end' %name)

p1=Process(target=piao,args=('egon',))
p2=Process(target=piao,args=('alex',))
p3=Process(target=piao,args=('yuanhao',))
p4=Process(target=piao,args=('wupeiqi',))

p1.start()
p2.start()
p3.start()
p4.start()

#有的同学会有疑问:既然join是等待进程结束,那么我像下面这样写,进程不就又变成串行的了吗?
#当然不是了,必须明确:p.join()是让谁等?
#很明显p.join()是让主线程等待p的结束,卡住的是主线程而绝非进程p,

#详细解析如下:
#进程只要start就会在开始运行了,所以p1-p4.start()时,系统中已经有四个并发的进程了
#而我们p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键
#join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其余p2,p3,p4仍然在运行,等#p1.join结束,可能p2,p3,p4早已经结束了,这样p2.join,p3.join.p4.join直接通过检测,无需等待
# 所以4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间
p1.join()
p2.join()
p3.join()
p4.join()

print('主线程')


#上述启动进程与join进程可以简写为
# p_l=[p1,p2,p3,p4]
# 
# for p in p_l:
#     p.start()
# 
# for p in p_l:
#     p.join()

2、其他方法(了解)

(1)terminate与is_alive
#进程对象的其他方法一:terminate,is_alive
from multiprocessing import Process
import time
import random

class Piao(Process):
    def __init__(self,name):
        self.name=name
        super().__init__()

    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,5))
        print('%s is piao end' %self.name)


p1=Piao('egon1')
p1.start()

p1.terminate() # 关闭进程,不会立即关闭,只是给操作系统发指令关闭,这是异步的,可能系统还没执行这个指令,下面的代码就运行完了,所以is_alive立刻查看的结果可能还是存活
print(p1.is_alive()) # 结果为True

time.sleep(0.1)
print(p1.is_alive()) #结果为False
(2)name与pid
from multiprocessing import Process
import time
import random
class Piao(Process):
    def __init__(self,name):
        # self.name=name
        # super().__init__() #Process的__init__方法会执行self.name=Piao-1,
        #                    #所以加到这里,会覆盖我们的self.name=name

        # 为我们开启的进程设置名字的做法
        super().__init__()
        self.name=name

    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,3))
        print('%s is piao end' %self.name)


p=Piao('egon')
p.start()
print('开始')
print(p.pid) # 查看p这个子进程的pid
(3)os.getpid和os.getppid

os.getpid:获取当前进程的进程号
os.getppid:获取当前进程的父进程的进程号

from multiprocessing import Process
import time
import random
import os
class Piao(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,3))
        print('%s is piao end' %self.name)
        print(os.getpid()) # 子进程的pid,结果:8975
        print(os.getppid()) # 主进程的pid,结果:8974

p=Piao('egon')
p.start()
p.join()
print(os.getpid()) # 主进程的pid,结果:8974
print(os.getppid()) # pycharm主进程的pid,结果:8172

Python并发编程(二):多进程(实战基础篇)_第1张图片验证8172是pycharm的进程号:
在这里插入图片描述

七、僵尸进程与孤儿进程、守护进程

1、僵尸进程与孤儿进程(了解)

1. 僵尸进程(有害)
  僵尸进程:僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。

我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。

因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息。机制就不讲了!

任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。

2. 孤儿进程(无害)

  孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程)

2、守护进程

主进程创建守护进程

1. 守护进程会在主进程代码执行结束后就终止

2. 守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

#主进程代码运行完毕,守护进程就会结束
from multiprocessing import Process
from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


p1=Process(target=foo)
p2=Process(target=bar)

p1.daemon=True
p1.start()
p2.start()
print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止

# 执行结果:
123
main-------
456
end456
# 主进程结束后,会等待所有非守护进程执行完毕后,再回收各种资源,因此end456是会执行到的,但是主进程完结,p1进程也就凉了,因此end123并不会打印!

八、socket通信实现并发(并不是socketserver)

1、初级版本实现方法

from socket import *
from multiprocessing import Process

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 开启重用本地本地地址、本地端口,慎用,最好不用,因为这里只是学习,可以使用!
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__': #windows下start进程一定要写到这下面
    while True:
        conn,client_addr=server.accept()
        p=Process(target=talk,args=(conn,client_addr))
        p.start()

多个client端:

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))

这么实现有没有问题???

每来一个客户端,都在服务端开启一个进程,如果并发来一个万个客户端,要开启一万个进程吗,你自己尝试着在你自己的机器上开启一万个,10万个进程试一试。
解决方法:进程池

九、进程池

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:

1. 很明显需要并行执行的任务通常要远大于核数
2. 一个操作系统不可能无限开启进程,通常有几个核就开几个进程
3. 进程开启过多,效率反而会下降('开启进程是需要占用系统资源的,而且开启多于核数目的进程也无法做到并行,只能通过时间片轮转+多级反馈队列实现宏观的并发'# 例如:当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数… ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。

1、创建进程池的类详解

Pool(numprocess,initializer,initargs): # 创建进程池
'numprocess':要创建的进程数,如果省略,将默认使用cpu_count()的值
'initializer':是每个工作进程启动时要执行的可调用对象,默认为None
'initargs':是要传给initializer的参数组

# 如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程
# 主要方法
p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()

p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
   
p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成

P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
# 其他方法(了解部分)
方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
obj.ready():如果调用完成,返回True
obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
obj.wait([timeout]):等待结果变为可用。
obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数

2、进程池的应用

(1)同步调用apply
from multiprocessing import Pool
import os,time
def work(n):
    print('%s run' %os.getpid())
    time.sleep(3)
    return n**2

if __name__ == '__main__':
    p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
    res_l=[]
    for i in range(10):
        res=p.apply(work,args=(i,)) #同步调用,直到本次任务执行完毕拿到res,等待任务work执行的过程中可能有阻塞也可能没有阻塞,但不管该任务是否存在阻塞,同步调用都会在原地等着,只是等的过程中若是任务发生了阻塞就会被夺走cpu的执行权限
        res_l.append(res)
    print(res_l)
(2)异步调用apply_async
from multiprocessing import Pool
import os,time
def work(n):
    print('%s run' %os.getpid())
    time.sleep(3)
    return n**2

if __name__ == '__main__':
    p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
    res_l=[]
    for i in range(10):
        res=p.apply_async(work,args=(i,)) #同步运行,阻塞、直到本次任务执行完毕拿到res
        res_l.append(res)

    #异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果,否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
    p.close()
    p.join()
    for res in res_l:
        print(res.get()) #使用get来获取apply_async的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,获取了结果才能往下执行,也根本无需get

# 有人认为apply_async也不用get,原因如下:
因为进程的任务work这个函数返回的本来就是执行结果,那么p.apply_async的返回结果也应该是执行结果,为什么还要get?
其实不然,print(res),执行结果如下:
<multiprocessing.pool.ApplyResult object at 0x10792edd0>

(3)详解:apply_async与apply

#一:使用进程池(异步调用,apply_async)
#coding: utf-8
from multiprocessing import Process,Pool
import time

def func(msg):
    print( "msg:", msg)
    time.sleep(1)
    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, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res)
    print("==============================>") #没有后面的join,或get,则程序整体结束,进程池中的任务还没来得及全部执行完也都跟着主进程一起结束了

    pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

    print(res_l) #看到的是对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果
    for i in res_l:
        print(i.get()) #使用get来获取apply_async的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get

#二:使用进程池(同步调用,apply)
#coding: utf-8
from multiprocessing import Process,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)

3、socket通信实现并发(进程池升级版)

服务端:

# Pool内的进程数默认是cpu核数,假设为4(查看方法os.cpu_count()),为什么?,因为cpu核数就是可以并行的进程数,再多就只能并发了。
# 开启6个客户端,会发现2个客户端处于等待状态
# 在每个进程内查看pid,会发现pid使用为4个,即多个客户端公用4个进程
from socket import *
from multiprocessing import Pool
import os

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn,client_addr):
    print('进程pid: %s' %os.getpid())
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__':
    p=Pool()
    while True:
        conn,client_addr=server.accept()
        p.apply_async(talk,args=(conn,client_addr)) # async异步
        # p.apply(talk,args=(conn,client_addr)) # 同步的话,则同一时间只有一个客户端能访问

客户端:

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))

注意:并行开启多个客户端,服务端同一时间只有3个不同的pid,干掉一个客户端,另外一个客户端才会进来,被3个进程之一处理。

4、异步回调函数

需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则指定一个函数去处理该结果,该函数即回调函数

第2点:使用进程池维护固定数目的进程(升级版)中使用了p.apply_async(talk,args=(conn,client_addr))这个是异步的,但是它并没有指定回调函数。

下面来说一个异步回调函数的例子:

from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def pasrse_page(res):
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]
    p=Pool(3) # 指定进程池的大小 
    res_l=[]
    for url in urls:
        res=p.apply_async(get_page,args=(url,),callback=pasrse_page) 
        # callback=pasrse_page,主进程指定异步的回调函数是pasrse_page函数
        # 异步回调函数的参数是子进程的处理完任务的返回结果,这里也就是get_page函数中的return {'url':url,'text':respone.text}。
        res_l.append(res)

    p.close()
    p.join()
    print([res.get() for res in res_l]) # 拿到的是get_page的结果,其实完全没必要拿该结果,该结果已经传给回调函数处理了
    # 上面的[res.get() for res in res_l]大家可能看不懂,这个是列表生成式,for res in res_l,因为不知道队列中元素的数目,所以for循环控制get的次数,get的结果放入列表。

注意:如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数,只有在每一个子进程返回的结果都需要单独处理才需要异步回调函数

你可能感兴趣的:(#,并发编程)