Python、进程间通信、进程池、协程

进程间通信

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。

进程队列queue

不同于线程queue,进程queue的生成是用multiprocessing模块生成的。

在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间。

示例1:

 1 import multiprocessing
 2 def foo():
 3     q.put([11,‘hello‘,True])
 4     print(q.qsize())
 5
 6 q=multiprocessing.Queue() #全局定义一个q进程队列,在产生子进程时候会在子进程里生成,可以指定最大数,限制队列长度
 7 if __name__ == ‘__main__‘:
 8     p=multiprocessing.Process(target=foo,args=()) #因为名称空间不同,子进程的主线程创建的q队列,主进程get不到,所以会阻塞住
 9     p.start()
10     # foo() #主进程执行一下函数就可以访问到了
11     print(q.get())

示例2:

 1 import multiprocessing
 2
 3 def foo():
 4     q.put([11,‘hello‘,True])
 5     print(q.qsize())
 6
 7 if __name__ == ‘__main__‘:
 8     q = multiprocessing.Queue() #主进程创建一个q进程队列
 9     p=multiprocessing.Process(target=foo,args=()) #因为名称空间不同,子进程的主线程找不到q队列,所以会报错提示没有q
10     p.start()
11     print(q.get())

示例3:

 1 import multiprocessing
 2
 3 def foo(argument):      #定义函数处理进程队列
 4     argument.put([11,‘hello‘,True])
 5     print(argument.qsize())
 6 q = multiprocessing.Queue() #全局定义一个进程队列
 7 print(‘test‘)
 8
 9 if __name__ == ‘__main__‘:
10     x = multiprocessing.Queue()   #主进程定义一个进程队列
11     p=multiprocessing.Process(target=foo,args=(x,))     #主进程把值传给子进程就可以处理了
12     p.start()
13     print(x.get())
14     # foo(q)
15     # print(q.get())

常用方法

q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

其他方法

q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为

另一个创建进程队列的类

http://www.cnblogs.com/zero527/p/7211909.html

管道pipe

管道就是管道,就像生活中的管道,两头都能进能出

默认管道是全双工的,如果创建管道的时候映射成False,左边只能用于接收,右边只能用于发送,类似于单行道

最简单的管道双向通信示例:

 1 import multiprocessing
 2
 3 def foo(sk):
 4     sk.send(‘hello world‘)
 5     print(sk.recv())
 6
 7 if __name__ == ‘__main__‘:
 8     conn1,conn2=multiprocessing.Pipe()    #开辟两个口,都是能进能出,括号中如果False即单向通信
 9     p=multiprocessing.Process(target=foo,args=(conn1,))  #子进程使用sock口,调用foo函数
10     p.start()
11     print(conn2.recv())  #主进程使用conn口接收
12     conn2.send(‘hi son‘) #主进程使用conn口发送

常用方法

conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象注意:send()和recv()方法使用pickle模块对对象进行序列化

其他方法

conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法,不用的时候两边都要close

conn1.fileno():返回连接使用的整数文件描述符

conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。

conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。

conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收    

conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。

注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。

生产者消费者关闭某端点

共享数据manage

Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据

注:进程间通信应该尽量避免使用共享数据的方式

共享数据:列表

 1 from multiprocessing import Manager,Process
 2 def foo(l,i):
 3     l.append(i**i)
 4 if __name__ == ‘__main__‘:
 5     man=Manager()
 6     ml=man.list([11,22,33])
 7     l=[]
 8     for i in range(5):
 9         p=Process(target=foo,args=(ml,i))
10         p.start()
11         l.append(p)
12     for i in l: #必须要join,不然会执行报错,处理一个数据必须要一个个来,不能同时处理一个数据
13         i.join()
14     print(ml)

共享数据:字典

 1 from multiprocessing import Manager,Process
 2 def foo(d,k,v):
 3     d[k]=v
 4 if __name__ == ‘__main__‘:
 5     man=Manager()
 6     md=man.dict({‘name‘:‘bob‘})
 7     l=[]
 8     for i in range(5):
 9         p=Process(target=foo,args=(md,i,‘a‘))
10         p.start()
11         l.append(p)
12     for i in l: #必须要join,不然会执行报错,处理一个数据必须要一个个来,不能同时处理一个数据
13         i.join()
14     print(md)

进程池

开多进程是为了并发,通常有几个cpu核心就开几个进程,但是进程开多了会影响效率,主要体现在切换的开销,所以引入进程池限制进程的数量。

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

示例:

 1 from multiprocessing import Pool
 2 import time
 3
 4 def foo(n):
 5     print(n)
 6     time.sleep(1)
 7
 8 if __name__ == ‘__main__‘:
 9     pool_obj=Pool(5)    #
10     for i in range(47):
11         # pool_obj.apply_async(func=foo,args=(i,))
12         pool_obj.apply(func=foo,args=(i,))    #子进程的生成是靠进程池对象维护的
13         # apply同步,子进程一个个执行
14         # apply_async异步,多个子进程一起执行
15     pool_obj.close()
16     pool_obj.join()
17     print(‘ending‘)

常用方法:

pool_obj.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()
pool_obj.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
pool_obj.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
pool_obj.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用

其他方法:

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

协程

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。

一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

注意:

  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程一旦遇到io就被迫交出cpu执行权限,切换其他线程运行)

  2. 单线程内开启协程,一旦遇到io,从应用程序级别(而非操作系统)控制切换

协程优点:

  1.  协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

  2. 单线程内就可以实现并发的效果,最大限度地利用cpu

协程缺点:

  1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

  2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

yield实现协程并发

 1 import time
 2 def consumer():
 3     r=‘‘
 4     while True:
 5         n=yield r
 6         if not n:
 7             return
 8         print(‘[CONSUMER] ←← Consuming %s...‘ % n)
 9         time.sleep(1)
10         r=‘200 Ok‘
11
12 def produce(c):
13     next(c) #1.启动生成器
14     n=0
15     while n < 5:
16         n=n+1
17         print(‘[PRODUCER] →→ Producing %s...‘ % n)
18         cr=c.send(n)
19         #2.将n传入到consumer的对象,yield接收到传入值开始执行代码,遇到yield执行代码返回r的值
20         print(‘[PRODUCER] Consumer return: %s‘ % cr)
21     #3.produce没有值了,关闭整个过程
22     c.close()
23
24 if __name__ == ‘__main__‘:
25     c=consumer()    #生成生成器对象
26     produce(c)      #执行调用

greenlet框架实现协程(封装yield的基础库)

greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库。

示例1:

 1 from greenlet import  greenlet
 2 def foo():
 3     print(‘ok1‘)
 4     g2.switch() #阻断
 5     print(‘ok3‘)
 6     g2.switch()
 7 def bar():
 8     print(‘ok2‘)
 9     g1.switch()
10     print(‘ok4‘)
11
12 g1=greenlet(foo)    #生成foo函数的greenlet对象
13 g2=greenlet(bar)    #生成bar函数的greenlet对象
14 g1.switch() #1、执行g1对象,打印ok1
15             #2、遇到g2.switch(),转到g2执行打印ok2
16             #3、遇到g1.switch(),转到g1的阻断处继续执行打印ok3
17             #4、遇到g2.switch(),转到g2执行打印ok4

示例2:

 1 def eat(name):
 2     print(‘%s eat food 1‘ %name)
 3     gr2.switch(‘bob‘)
 4     print(‘%s eat food 2‘ %name)
 5     gr2.switch()
 6 def play_phone(name):
 7     print(‘%s play 1‘ %name)
 8     gr1.switch()
 9     print(‘%s play 2‘ %name)
10
11 gr1=greenlet(eat)
12 gr2=greenlet(play_phone)
13 gr1.switch(name=‘natasha‘)#可以在第一次switch时传入参数,以后都不需要

这种方法不会节省时间,因为不是io操作,而greenlet遇到io操作不会跳转,仍然要io阻断

基于greenlet框架的高级库gevent模块

gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

简单示例:

 1 import gevent
 2 def foo():
 3     print(‘ok1‘)
 4     gevent.sleep(4) #模拟io操作
 5     print(‘ok3‘)
 6 def bar():
 7     print(‘ok2‘)
 8     gevent.sleep(2)
 9     print(‘ok4‘)
10
11 g1=gevent.spawn(foo)
12 g2=gevent.spawn(bar)
13 gevent.joinall([g1,g2]) #全部阻塞,或者单独一个个join

spawn括号内第一个参数是函数名,如foo,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数foo的

注意:

gevent.sleep(4)模拟的是gevent可以识别的io阻塞,

而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

1 #补丁
2 from gevent import monkey
3 monkey.patch_all()

必须放到被打补丁者的前面,如time,socket模块之前

或者我们干脆记忆成:要用gevent,需要将补丁放到文件的开头

爬虫示例:

 1 from gevent import monkey;monkey.patch_all()
 2 import gevent
 3 import requests
 4 import time
 5
 6 def get_page(url):
 7     print(‘GET: %s‘ %url)
 8     response=requests.get(url)
 9     if response.status_code == 200:
10         print(‘%d bytes received from %s‘ %(len(response.text),url))
11
12
13 start_time=time.time()
14 gevent.joinall([
15     gevent.spawn(get_page,‘https://www.python.org/‘),
16     gevent.spawn(get_page,‘https://www.yahoo.com/‘),
17     gevent.spawn(get_page,‘https://github.com/‘),
18 ])
19 stop_time=time.time()
20 print(‘run time is %s‘ %(stop_time-start_time))

时间: 2024-10-06 02:51:01

Python、进程间通信、进程池、协程的相关文章

python的进程/线程/协程

1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有一个线程被执行. 多线程的特点: 线程比进程更轻量级,创建一个线程要比创建一个进程快10-100倍. 线程共享全局变量. 由于GIL的原因,当一个线程遇到IO操作时,会切换到另一个线程,所以线程适合IO密集型操作. 在多核cpu系统中,最大限度的利用多核,可以开启多个线程,开销比进程小的多,但是这并

进程池 协程 与I/O模型

一.进程池与线程池 进程池:限制进程创建的数量,使用时直接从进程池,获取空闲进程去执行任务,减少创建和销毁进程带来的时间消耗:如果进程的数量没有达到最大数量,且没有空闲进程去执行  任务,就会再创建一个新的进程,放入进程池去执行任务:如果进程池中的进程数量达到最大数量,没有空闲进程去,这是任务需要等待某个进程成为空闲进程之后, 再去执行任务. from concurrent.futures import ProcessPoolExecutor import time import os # 池子

多任务-python实现-进程,协程,线程总结(2.1.16)

目录 1.类比 2.总结 关于作者 @ 1.类比 一个生产玩具的工厂: 一个生产线成为一个进程,一个生产线有多个工人,所以工人为线程 单进程-多线程:一条生产线,多个工人 多进程-多线程:多条生产线,多个工人 协程:工人空闲的时候安排做其他事 2.总结 1.进程是资源分配的单位 2.线程为操作系统调度的单位 3.进程切换需要的资源很大,效率很低 4.线程需要的资源一般,效率一般(不考虑GIL) 5.协程切换的任务资源很小,效率高 6.多进程,多线程根据cpu核数不同可能是并行的,但协程是在一个线

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selectors模块 # Python队列/RabbitMQ队列

1 # 进程/线程/协程 2 # IO:同步/异步/阻塞/非阻塞 3 # greenlet gevent 4 # 事件驱动与异步IO 5 # Select\Poll\Epoll异步IO 以及selectors模块 6 # Python队列/RabbitMQ队列 7 8 ############################################################################################## 9 1.什么是进程?进程和程序之间有什么

Python:线程、进程与协程(4)——multiprocessing模块(1)

multiprocessing模块是Python提供的用于多进程开发的包,multiprocessing包提供本地和远程两种并发,通过使用子进程而非线程有效地回避了全局解释器锁. (一)创建进程Process 类 创建进程的类,其源码在multiprocessing包的process.py里,有兴趣的可以对照着源码边理解边学习.它的用法同threading.Thread差不多,从它的类定义上就可以看的出来,如下: class Process(object):     '''     Proces

Python之路【第七篇】:线程、进程和协程

Python之路[第七篇]:线程.进程和协程 Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time   def show(arg):     time.sleep(1)     print 'thread'+str(arg)   for i in

Python菜鸟之路:Python基础-线程、进程、协程

上节内容,简单的介绍了线程和进程,并且介绍了Python中的GIL机制.本节详细介绍线程.进程以及协程的概念及实现. 线程 基本使用 方法1: 创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入 import threading import time def worker(): time.sleep(2) print("test") for i in range(5): t = threading.Thread(target=

python运维开发(十)----IO多路复用多线程、进程、协程

内容目录: python作用域 python2.7和python3.5的多继承区别 IO多路复用 多线程.进程.协程 python作用域  python中无块级作用域 if 1 == 1: name = 'jabe' print(name) #可以正常输出jabe #在python中无块级作用域 #在c#或者java中是不能这样使用的,提示name未定义的 python中以函数为作用域 def func(): name = 'jbae' func() print(name) #会提示name为定

Python:线程、进程与协程(1)——概念

最近的业余时间主要放在了学习Python线程.进程和协程里,第一次用python的多线程和多进程是在两个月前,当时只是简单的看了几篇博文然后就跟着用,没有仔细去研究,第一次用的感觉它们其实挺简单的,最近这段时间通过看书, 看Python 中文官方文档等等相关资料,发现并没有想想中的那么简单,很多知识点需要仔细去理解,Python线程.进程和协程应该是Python的高级用法.Python的高级用法有很多,看看Python 中文官方文档就知道了,当然有时间看看这些模块是怎么实现的对自己的提高是很有帮

Python:线程、进程与协程(3)——Queue模块及源码分析

Queue模块是提供队列操作的模块,队列是线程间最常用的交换数据的形式.该模块提供了三种队列: Queue.Queue(maxsize):先进先出,maxsize是队列的大小,其值为非正数时为无线循环队列 Queue.LifoQueue(maxsize):后进先出,相当于栈 Queue.PriorityQueue(maxsize):优先级队列. 其中LifoQueue,PriorityQueue是Queue的子类.三者拥有以下共同的方法: qsize():返回近似的队列大小.为什么要加"近似&q