阅读目录
一. cpython并发编程之多进程
1.1 multiprocessing模块介绍
1.2 Process类的介绍
1.3 Process类的使用
1.4 进程间通信(IPC)方式一:队列
1.5 进程间通信(IPC)方式二:管道(了解部分)
1.6 进程间通信方式三:共享数据
1.7 进程同步(锁),信号量,事件...
1.8 进程池
二. python并发编程之多线程
2.1 threading模块
2.2 Python GIL(Global Interpreter Lock)
2.3 同步锁
2.4 死锁与递归锁
2.5 信号量Semahpore
2.6 事件Event
2.7 条件Condition(了解)
2.8 定时器Timer
2.9 线程queue
2.10 Python标准模块--concurrent.futures
三. 协程
四. 协程模块greenlet
五. gevent模块(单线程并发)
六. 综合应用
一. cpython并发编程之多进程
1.1 multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
强调: 与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
1.2 Process类的介绍
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None target表示调用对象,即子进程要执行的任务 args表示调用对象的位置参数元组,args=(1,2,'egon',) kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} name为子进程的名称
方法介绍:
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.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
1.3 Process类的使用
1.创建并开启子进程的两种方式
注: 在windows中Process()必须放到# if __name__ == '__main__':下
Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module.
If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources).
This is the reason for hiding calls to Process() inside
if __name__ == "__main__"
since statements inside this if-statement will not get called upon import.
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。
这是隐藏对Process()内部调用的原理,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
#! /usr/bin/env python # -*- coding: utf-8 -*- # __author__ = "shuke" # Date: 2017/6/26 0026 import time import random from multiprocessing import Process def talk(name): print("%s is say 'Hello'" % name) time.sleep(3) print("talking end") if __name__ == '__main__': p1=Process(target=talk,args=('Shuke',)) # args是元组的形式,必须加逗号 p2=Process(target=talk,args=('Tom',)) p3=Process(target=talk,args=('Eric',)) p4=Process(target=talk,args=('Lucy',)) p1.start() p2.start() p3.start() p4.start()
import time import random from multiprocessing import Process class Talk(Process): # 继承Process类 def __init__(self,name): super(Talk, self).__init__() # 继承父类__init__方法 self.name=name def run(self): # 必须实现一个run方法,规定 print("%s is say 'Hello'" % self.name) time.sleep(random.randint(1,3)) print("%s talking end"% self.name) if __name__ == '__main__': p1=Talk('Shuke') p2=Talk('Eric') p3=Talk('Tome') p4=Talk('Lucy') p1.start() # start方法会自动调用run方法运行 p2.start() p3.start() p4.start() print("主线程") ''' 执行结果: 主线程 Shuke is say 'Hello' Lucy is say 'Hello' Tome is say 'Hello' Eric is say 'Hello' Tome talking end Eric talking end Lucy talking end Shuke talking end '''
并发实现socket通信示例
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', 8081)) 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, addr = server.accept() p = Process(target=talk, args=(conn, addr)) p.start()
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8081)) 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万个进程试一试。
解决方法:进程池
2. Process对象的其他方法和属性
进程对象的其他方法一:terminate,is_alive
import time import random from multiprocessing import Process class Talk(Process): # 继承Process类 def __init__(self,name): super(Talk, self).__init__() # 继承父类__init__方法 self.name=name def run(self): # 必须实现一个run方法,规定 print("%s is say 'Hello'" % self.name) time.sleep(random.randint(1,3)) print("%s talking end"% self.name) if __name__ == '__main__': p1=Talk('Shuke') p1.start() # start方法会自动调用run方法运行 p1.terminate() # 关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活 print(p1.is_alive())# True time.sleep(1) # 模拟CPU调度的延时 print("====分割线====") print(p1.is_alive())# False ''' 执行结果: True ====分割线==== False '''
进程对象的其他方法二:p1.daemon=True,p1.join
import time import random from multiprocessing import Process class Talk(Process): def __init__(self,name): super(Talk, self).__init__() self.name=name def run(self): print("%s is say 'Hello'" % self.name) time.sleep(random.randint(1,3)) print("%s talking end"% self.name) if __name__ == '__main__': p1=Talk('Shuke') p1.daemon = True # 一定要在p1.start()前设置,设置p1为守护进程,禁止p1创建子进程,并且父进程结束,p1跟着一起结束 p1.start() # start方法会自动调用run方法运行 p1.join(0.0001) # 等待p1停止,等0.0001秒就不再等了
剖析p1.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() p1.join() p2.join() p3.join() p4.join() print('主线程') #疑问:既然join是等待进程结束,那么我像下面这样写,进程不就又变成串行的了吗? #当然不是了 #注意:进程只要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花费的总时间仍然是耗费时间最长的那个进程运行的时间 #上述启动进程与join进程可以简写为 p_l=[p1,p2,p3,p4] for p in p_l: p.start() for p in p_l: p.join()
进程对象的其他属性:name,pid
import time import random from multiprocessing import Process class Talk(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 say 'Hello'" % self.name) time.sleep(random.randint(1,3)) print("%s talking end"% self.name) if __name__ == '__main__': p1=Talk('Shuke') p1.start() # start方法会自动调用run方法运行 print("====") print(p1.pid) # 查看pid ''' 执行结果: ==== 20484 Shuke is say 'Hello' Shuke talking end '''
3. 进程同步(锁)
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的
#多进程共享一个打印终端(用python2测试看两个进程同时往一个终端打印,出现打印到一行的错误) from multiprocessing import Process import time class Logger(Process): def __init__(self): super(Logger,self).__init__() def run(self): print(self.name) for i in range(1000000): l=Logger() l.start()
#多进程共享一套文件系统 from multiprocessing import Process import time,random def work(f,msg): f.write(msg) f.flush() f=open('a.txt','w') #在windows上无法把f当做参数传入,可以传入一个文件名,然后在work内用a+的方式打开文件,进行写入测试 for i in range(5): p=Process(target=work,args=(f,str(i))) p.start()
注: 既然可以用文件共享数据,那么进程间通信用文件作为数据传输介质就可以了啊,可以,但是有问题:
1.效率
2.需要自己加锁处理
需知:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。
进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理。所以,就让我们用文件当做数据库,模拟抢票,(Lock互斥锁),见下文抢票示例。
学习了通过使用共享的文件的方式,实现进程直接的共享,即共享数据的方式,这种方式必须考虑周全同步、锁等问题。而且文件是操作系统提供的抽象,可以作为进程直接通信的介质,与mutiprocess模块无关。
但其实mutiprocessing模块为我们提供了基于消息的IPC通信机制:队列和管道。
IPC机制中的队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可扩展性。
1.4 进程间通信(IPC)方式一:队列
进程彼此之间互相隔离,要实现进程间通信,即IPC,multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的,广泛应用在分布式系统中。
Queue模块有三种队列及构造函数:
1. Python Queue模块的FIFO队列先进先出。 class Queue.Queue(maxsize)
2. LIFO类似于堆,即先进后出。 class Queue.LifoQueue(maxsize)
3. 还有一种是优先级队列级别越低越先出来。 class Queue.PriorityQueue(maxsize)
Queue类(创建队列)
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递,底层是以管道和锁的方式实现的。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
主要方法:
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.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作
其他方法:
q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
应用:
''' multiprocessing 模块支持进程间通信的两种主要形式:管道和队列 都是基于消息传递实现的,都是队列接口 ''' from multiprocessing import Process,Queue import time q=Queue(5) q.put([1,2,3]) q.put(('a','b','c')) q.put(100) q.put("Hello World") q.put({'name':'shuke'}) # q.put('队列满了') # 如果队列元素满了,后续put进入队列的数据将会处于等待状态,直到队列的元素被消费,才可以加入 print(q.qsize()) # 5; 返回队列的大小 print(q.full()) # True print(q.get()) # [1, 2, 3] print(q.get()) # ('a', 'b', 'c') print(q.get()) # 100 print(q.get()) # Hello World print(q.get()) # {'name': 'shuke'} # print(q.get()) # 如果队列元素全部被消费完成,会一直卡住,直到队列中被放入新的元素 print(q.empty()) # True
生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
from multiprocessing import Process,Queue import time import random def producer(seq,q,name): for item in seq: time.sleep(random.randint(1,3)) q.put(item) print("%s 生产者生产了: %s"%(name,item)) def consumer(q,name): while True: time.sleep(random.randint(1,3)) res=q.get() print("%s 消费者消费了: %s"%(name,res)) if __name__ == '__main__': q=Queue() seq=("苹果%s"% i for i in range(5)) p=Process(target=consumer,args=(q,'Tom')) # 以元组的方式传参 p.start() producer(seq,q,'shuke') print("=====主线程=====") ''' 执行结果: shuke 生产者生产了: 苹果0 Tom 消费者消费了: 苹果0 shuke 生产者生产了: 苹果1 Tom 消费者消费了: 苹果1 shuke 生产者生产了: 苹果2 shuke 生产者生产了: 苹果3 Tom 消费者消费了: 苹果2 shuke 生产者生产了: 苹果4 =====主线程===== Tom 消费者消费了: 苹果3 Tom 消费者消费了: 苹果4 '''
# 生产者发送结束标志给消费者 from multiprocessing import Process,Queue import time import random def producer(seq,q,name): for item in seq: time.sleep(random.randint(1,3)) q.put(item) print("%s 生产者生产了: %s"%(name,item)) def consumer(q,name): while True: time.sleep(random.randint(1,3)) res=q.get() if res is None:break print("%s 消费者消费了: %s"%(name,res)) if __name__ == '__main__': q=Queue() seq=("苹果%s"% i for i in range(5)) c=Process(target=consumer,args=(q,'Tom')) # 以元组的方式传参 c.start() producer(seq,q,'shuke') q.put(None) c.join() # 主线程等待直到c消费者进程运行结束再继续往下运行 print("=====主线程=====") ''' 执行结果: shuke 生产者生产了: 苹果0 Tom 消费者消费了: 苹果0 shuke 生产者生产了: 苹果1 Tom 消费者消费了: 苹果1 shuke 生产者生产了: 苹果2 Tom 消费者消费了: 苹果2 shuke 生产者生产了: 苹果3 Tom 消费者消费了: 苹果3 shuke 生产者生产了: 苹果4 Tom 消费者消费了: 苹果4 =====主线程===== '''
JoinableQueue类 (创建队列的另外一个类)
JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的消费者通知生产者队列已经被成功处理,通知进程是使用共享的信号和条件变量来实现的。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
- q.task_done(): 使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常。
- q.join(): 生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止。
from multiprocessing import Process,JoinableQueue import time import random def producer(seq,q,name): for item in seq: q.put(item) print("%s 生产者生产了: %s"%(name,item)) q.join() # 生产者调用此方法进行阻塞 def consumer(q,name): while True: res=q.get() if res is None:break print("%s 消费者消费了: %s"%(name,res)) q.task_done() # 使用者使用此方法发出信号,表示q.get()的返回元素已经被消费处理。 if __name__ == '__main__': q=JoinableQueue() seq=("苹果%s"% i for i in range(5)) c=Process(target=consumer,args=(q,'Tom')) # 以元组的方式传参 c.daemon=True # 在start之前进行设置为守护进程,在主线程停止时c也停止,但是不用担心,producer内调用q.join保证了consumer已经处理完队列中的所有元素 c.start() producer(seq,q,'shuke') print("=====主线程=====") ''' 执行结果: shuke 生产者生产了: 苹果0 Tom 消费者消费了: 苹果0 shuke 生产者生产了: 苹果1 Tom 消费者消费了: 苹果1 shuke 生产者生产了: 苹果2 Tom 消费者消费了: 苹果2 shuke 生产者生产了: 苹果3 Tom 消费者消费了: 苹果3 shuke 生产者生产了: 苹果4 Tom 消费者消费了: 苹果4 =====主线程===== '''
from multiprocessing import Process,JoinableQueue import time import random def producer(seq,q,name): for item in seq: time.sleep(random.randint(1,3)) q.put(item) print("%s 生产者生产了: %s"%(name,item)) q.join() def consumer(q,name): while True: time.sleep(random.randint(1, 3)) res=q.get() if res is None:break print("%s 消费者消费了: %s"%(name,res)) q.task_done() if __name__ == '__main__': q=JoinableQueue() seq=("苹果%s"% i for i in range(5)) c1=Process(target=consumer,args=(q,'消费者1')) # 以元组的方式传参 c2=Process(target=consumer,args=(q,'消费者2')) c3=Process(target=consumer,args=(q,'消费者3')) c1.daemon=True # 在start之前进行设置为守护进程,在主线程停止时c也停止,但是不用担心,producer内调用q.join保证了consumer已经处理完队列中的所有元素 c2.daemon=True c3.daemon=True c1.start() c2.start() c3.start() producer(seq,q,'shuke') print("=====主线程=====") ''' 执行结果: shuke 生产者生产了: 苹果0 消费者3 消费者消费了: 苹果0 shuke 生产者生产了: 苹果1 消费者1 消费者消费了: 苹果1 shuke 生产者生产了: 苹果2 消费者2 消费者消费了: 苹果2 shuke 生产者生产了: 苹果3 消费者1 消费者消费了: 苹果3 shuke 生产者生产了: 苹果4 消费者3 消费者消费了: 苹果4 =====主线程===== '''
from multiprocessing import Process,JoinableQueue import time import random def producer(seq,q,name): for item in seq: # time.sleep(random.randint(1,3)) q.put(item) print("%s 生产者生产了: %s"%(name,item)) q.join() def consumer(q,name): while True: # time.sleep(random.randint(1, 3)) res=q.get() if res is None:break print("%s 消费者消费了: %s"%(name,res)) q.task_done() if __name__ == '__main__': q=JoinableQueue() seq=["苹果%s"% i for i in range(5)] c1=Process(target=consumer,args=(q,'消费者1')) # 以元组的方式传参 c2=Process(target=consumer,args=(q,'消费者2')) c3=Process(target=consumer,args=(q,'消费者3')) c1.daemon=True # 在start之前进行设置为守护进程,在主线程停止时c也停止,但是不用担心,producer内调用q.join保证了consumer已经处理完队列中的所有元素 c2.daemon=True c3.daemon=True c1.start() c2.start() c3.start() # producer(seq,q,'shuke') # 也可以是下面三行的形式,开启一个新的子进程当生产者,不用主线程当生产者 p=Process(target=producer,args=(seq,q,'shuke')) # 注意此处参数seq为列表 p.start() p.join() print("=====主线程=====") ''' 执行结果: shuke 生产者生产了: 苹果0 shuke 生产者生产了: 苹果1 消费者3 消费者消费了: 苹果0 shuke 生产者生产了: 苹果2 消费者2 消费者消费了: 苹果1 消费者3 消费者消费了: 苹果2 shuke 生产者生产了: 苹果3 消费者2 消费者消费了: 苹果3 shuke 生产者生产了: 苹果4 消费者3 消费者消费了: 苹果4 =====主线程===== '''
1.5 进程间通信(IPC)方式二:管道(了解部分)
管道也可以说是队列的另外一种形式,下面我们就开始介绍基于管道实现进程之间的消息传递
Pipe类(创建管道)
Pipe([duplex]): 在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
参数介绍:
dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。
方法介绍:
主要方法:
- conn1.recv(): 接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
- conn1.send(obj): 通过连接发送对象。obj是与序列化兼容的任意对象。
conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法。
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异常。
基于管道实现进程间通信 (与队列的方式是类似的,队列就是管道加锁实现的)
from multiprocessing import Process,Pipe import time def consumer(p,name): left,right = p left.close() while True: try: fruit = right.recv() print("%s 收到水果: %s" % (name,fruit)) except EOFError: right.close() break def producer(seq,p): left,right = p right.close() for item in seq: left.send(item) else: left.close() if __name__ == '__main__': left,right = Pipe() c1=Process(target=consumer,args=((left,right),'Tom')) c1.start() seq=(i for i in range(5)) producer(seq,(left,right)) right.close() left.close() c1.join() print("===主线程===") ''' 执行结果: Tom 收到水果: 0 Tom 收到水果: 1 Tom 收到水果: 2 Tom 收到水果: 3 Tom 收到水果: 4 ===主线程=== '''
注: 生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,除非消费者中也关闭了相同的管道端点。
from multiprocessing import Process,Pipe import time,os def adder(p,name): server,client=p client.close() while True: try: x,y=server.recv() except EOFError: server.close() break res=x+y server.send(res) print('server done') if __name__ == '__main__': server,client=Pipe() c1=Process(target=adder,args=((server,client),'c1')) c1.start() server.close() client.send((10,20)) print(client.recv()) client.close() c1.join() print('主进程')
注: send()和recv()方法使用pickle模块对对象进行序列化。
1.6 进程间通信方式三:共享数据
展望未来,基于消息传递的并发编程是大势所趋,即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中。
注: 进程间通信应该尽量避免使用本节所讲的共享数据的方式
进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的,虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此。
from multiprocessing import Process,Manager import os def foo(name,d,l): l.append(os.getpid()) d[name]=os.getpid() if __name__ == '__main__': with Manager() as manager: d=manager.dict({'name':'shuke'}) l=manager.list(['init',]) p_l=[] for i in range(5): p=Process(target=foo,args=('p%s' %i,d,l)) p.start() p_l.append(p) for p in p_l: p.join() #必须有join不然会报错 print(d) print(l) ''' 执行结果: {'p0': 62792, 'p4': 63472, 'name': 'shuke', 'p1': 60336, 'p3': 62704, 'p2': 63196} ['init', 60336, 62704, 62792, 63196, 63472] '''
1.7 进程同步(锁),信号量,事件...
模拟抢票(Lock-->互斥锁)
# 文件db的内容为:{"count":1} # 注意一定要用双引号,不然json无法识别 from multiprocessing import Process,Lock import json import time import random import os def work(filename,lock): #买票 # lock.acquire() with lock: # with语法下面的代码块执行完毕会自动释放锁 with open(filename,encoding='utf-8') as f: dic=json.loads(f.read()) # print('剩余票数: %s' % dic['count']) if dic['count'] > 0: dic['count']-=1 time.sleep(random.randint(1,3)) #模拟网络延迟 with open(filename,'w',encoding='utf-8') as f: f.write(json.dumps(dic)) print('%s 购票成功' %os.getpid()) else: print('%s 购票失败' %os.getpid()) # lock.release() if __name__ == '__main__': lock=Lock() p_l=[] for i in range(5): p=Process(target=work,args=('db',lock)) p_l.append(p) p.start() for p in p_l: p.join() print('主线程') ''' 执行结果: 63448 购票成功 13676 购票失败 61668 购票失败 63544 购票失败 17816 购票失败 主线程 '''
#互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去,如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。一旦释放,就有人可以获得一把锁 #信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念 from multiprocessing import Process,Semaphore import time,random def go_wc(sem,user): sem.acquire() print('%s 占到一个茅坑' %user) time.sleep(random.randint(0,3)) #模拟每个人拉屎速度不一样,0代表有的人蹲下就起来了 sem.release() if __name__ == '__main__': sem=Semaphore(5) p_l=[] for i in range(13): p=Process(target=go_wc,args=(sem,'user%s' %i,)) p.start() p_l.append(p) for i in p_l: i.join() print('============》')
# python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。 # 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。 clear:将“Flag”设置为False set:将“Flag”设置为True #_*_coding:utf-8_*_ #!/usr/bin/env python from multiprocessing import Process,Event import time,random def car(e,n): while True: if not e.is_set(): #Flase print('