本节内容:
1:生产者与消费者
2:进程调用两种
3:进程Process的方法
4:进程间的通信1 queue 队列
5:进程间的通信2 Pipe 管道
6:进程间的数据共享 Managers
7:进程同步
8:进程池
9:协程
1.生产者与消费者
生产者消费者模型:
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
import queue
import threading
import time
q = queue.Queue()
class Producer(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name = name
def run(self):
for i in range(1,10):
print('making....')
print("priducer %s make %s 包子."%(self.name,i))
q.put(i) ## 放进队列之后
q.task_done() ##发送一个信号,通知q里面有数据了
time.sleep(1)
class Customer(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name = name
def run(self):
while 1: ##一直在吃
q.join() ##当有信号来了我就去包子笼去取,
data = q.get()
print("customer %s eat %s baozi"%(self.name,data))
p = Producer("小当家")
c = Customer("B君")
p.start()
c.start()
2.进程的调用
进程的调用和线程的调用是一样的,有直接调用和类调用两种方式。
调用方式1:直接调用
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name,time.ctime())
if __name__ == '__main__':
p_list=[]
for i in range(3):
p = Process(target=f, args=('alvin',))
p_list.append(p)
p.start()
for i in p_list:
p.join()
print('end')
调用方式2:类调用
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self):
super(MyProcess, self).__init__()
#self.name = name
def run(self):
time.sleep(1)
print ('hello', self.name,time.ctime())
if __name__ == '__main__':
p_list=[]
for i in range(3):
p = MyProcess()
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('end')
3.进程Process的方法
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
group: 线程组,目前还没有实现,库引用中提示必须是None;
target: 要执行的方法;
name: 进程名;
args/kwargs: 要传入方法的参数。
实例方法:
is_alive():返回进程是否在运行。
join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
start():进程准备就绪,等待CPU调度
run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
terminate():不管任务是否完成,立即停止工作进程
属性:
daemon:和线程的setDeamon功能一样,不过就是进程的daemon的属性,使用方式:p.daemon = False
name:进程名字。
pid:进程号。
import time
from multiprocessing import Process
def foo(i):
time.sleep(1)
print (p.is_alive(),i,p.pid)
time.sleep(1)
if __name__ == '__main__':
p_list=[]
for i in range(10):
p = Process(target=foo, args=(i,))
#p.daemon=True
p_list.append(p)
for p in p_list:
p.start()
# for p in p_list:
# p.join()
print('main process end')
View Code
4:进程间的通信1 queue 队列
要实现的代码:子进程放数据 ,主线程取数据
import queue
import multiprocessing
def foo(q):
q.put(12)
# print('id q:',id(q))
if __name__ == '__main__':
# q = queue.Queue() ##这是一个线程队列,我要用进程队列
q = multiprocessing.Queue()
print('id q:', id(q))
p = multiprocessing.Process(target=foo,args=(q,)) ##创建的q是在主进程中,要想使用需要给子进程传过去;子进程复制了q的
p.start()
# p.join()
data = q.get() ##队列为空,就阻塞住
print(data)
5:进程间的通信2 Pipe 管道
Pipe有点类似socket的conn ;他是一个双向管道,parent_conn, child_conn = Pipe() 这样拿到了2个双向管道 ;
Pipe 一端给父亲 ,一端给儿子 ,然后父亲和儿子就可以进行通信了 ;
Pipe的send 和recv 和socket的接口类似,但是不走网络。所以不需要转字节;
import multiprocessing
def foo(conn):
conn.send("{'name':'yhy','content':'爸爸你好'}")
data = conn.recv()
print(data)
if __name__ == '__main__':
parent_conn,child_conn = multiprocessing.Pipe() ##创建了管道
p = multiprocessing.Process(target=foo,args=((child_conn,))) ##一端给儿子
p.start()
parent_conn.send('儿子在吗')
data = parent_conn.recv()
print(data)
6:进程间的数据共享 Managers
Queue和pipe只是实现了数据交互,并没实现数据共享,数据共享即一个进程去更改另一个进程的数据。
可以进程数据共享的数据结构有如下几种:但是要记住这些的数据结构都是要经过,manager封装过的数据类型。
types list, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Barrier
, Queue
, Value
and Array
.
from multiprocessing import Process, Manager
def f(d, l,n):
d[n] = '1'
d['2'] = 2
l.append(n)
# print("son process:",id(d),id(l))
if __name__ == '__main__':
with Manager() as manager: ##类似文件操作的with操作,我们可以不必手动的colose
d = manager.dict() ##字典是经过manager封装过的字典
l = manager.list(range(5))
# print("main process:",id(d),id(l))
p_list = []
for i in range(10):
p = Process(target=f, args=(d,l,i))
p.start()
p_list.append(p)
for res in p_list:
res.join()
print(d)
print(l)
# 输出:
# {0: '1', 1: '1', 2: '1', 3: '1', 4: '1', 5: '1', 6: '1', 7: '1', 8: '1', 9: '1', '2': 2}
# [0, 1, 2, 3, 4, 0, 1, 2, 3, 6, 4, 7, 8, 5, 9]
7:进程同步
需知:
什么是同步?
有资源在阻塞,处于一个阻塞的状态。
为什么有同步锁?
控制一次只允许一个线程对同一个数据进行操作 。
进程的数据是相互独立的,那我进程还用同步干什么?
说是这么多,但是多个进程,也会面临共用同一个资源的时候,比如说屏幕。
代码实现内容:开启十个进程依次打印print('hello world %s' % i)
from multiprocessing import Process, Lock
def f(l, i):
# with l.acquire():
print('hello world %s' % i)
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
输出:屏幕是共用的资源,进程0和进程1都想在屏幕打印信息,出现错乱 。
解决的办法:加锁,而锁是multiprocessing 下的Lock
from multiprocessing import Process, Lock
def f(l, i):
with l: ##with之后 自动acquire 自动release
print('hello world %s' % i)
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
8: 进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply 阻塞同步
- apply_async 异步状态
进程池,就是定义一个池了,我只开固定的几个进程去反复的执行某个动作。
from multiprocessing import Process,Pool
import time,os
def Foo(i):
time.sleep(3)
print(i)
return i+100
def Bar(arg): ##回调函数必须要有一个形参去,接收foo的返回值
print(arg)
print('hello')
if __name__ == '__main__':
pool = Pool() ##这就是池,定义最大为5个,要是不定义 那么默认是按你cpu的核数去跑。
for i in range(100):
#pool.apply(func=Foo, args=(i,)) ##提供了同步接口,串行
#pool.apply_async(func=Foo, args=(i,))
##回调函数,callback 就是某个动作或者函数执行成功之后再去执行的函数,回调函数是主进程执行的
pool.apply_async(func=Foo, args=(i,),callback=Bar)
pool.close() ##进程池的格式是死的,必须先close再join
pool.join()
print('end')
9:协程
协程,又称微线程,协程执行看起来有点像多线程,但是事实上协程就是只有一个线程,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显,此外因为只有一个线程,不需要多线程的锁机制,也不存在同时写变量冲突。协程的适用场景:当程序中存在大量不需要CPU的操作(IO)阻塞时。
默认线程和进程都是抢占式的,当我们开启多个的线程和进程时候,哪个会抢到cpu的执行,无法预料。 协程将cpu的执行流程交到了用户手上。 协程是也是多任务实现方式,它不需要多个进程或线程就可以实现多任务。
一、通过yield的简单实现:
import time
import queue
def consumer(name):
print("--->ready to eat baozi...")
while True:
new_baozi = yield
print("[%s] is eating baozi %s" % (name,new_baozi))
#time.sleep(1)
def producer():
r = con.__next__()
r = con2.__next__()
n = 0
while 1:
time.sleep(1)
print("\033[32;1m[producer]\033[0m is making baozi %s and %s" %(n,n+1) )
con.send(n)
con2.send(n+1)
n +=2
if __name__ == '__main__':
con = consumer("c1")
con2 = consumer("c2")
p = producer()
View Code
yield能实现协程,不过实现过程不易于理解,greenlet是在这方面做了改进。
二、Greenlet
from greenlet import greenlet
import time
def A():
while 1:
print('-------A-------')
time.sleep(0.5)
g2.switch()
def B():
while 1:
print('-------B-------')
time.sleep(0.5)
g1.switch()
g1 = greenlet(A) #创建协程g1
g2 = greenlet(B)
g1.switch() #跳转至协程g1
输出:
-------A------- -
------B-------
-------A-------
-------B-------
-------A------- ···
三、gevent:
- greenlet可以实现协程,不过每一次都要人为的去指向下一个该执行的协程,显得太过麻烦。python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent
- gevent每次遇到io操作,需要耗时等待时,会自动跳到下一个协程继续执行。
import gevent
def A():
while 1:
print('-------A-------')
gevent.sleep(1) #用来模拟一个耗时操作,注意不是time模块中的sleep
def B():
while 1:
print('-------B-------')
gevent.sleep(0.5) #每当碰到耗时操作,会自动跳转至其他协程
g1 = gevent.spawn(A) # 创建一个协程
g2 = gevent.spawn(B)
g1.join() #等待协程执行结束
g2.join()
输出:
-------A-------
-------B-------
-------B-------
-------A-------
-------B-------
-------B-------
-------A-------
-------B-------
-------B-------
···