并发编程--greenlet与gevent
什么是greenlet?
虽然CPython(标准Python)能够通过生成器来实现协程,但使用起来还并不是很方便。
与此同时,Python的一个衍生版 Stackless Python实现了原生的协程,它更利于使用。
于是,大家开始将 Stackless 中关于协程的代码单独拿出来做成了CPython的扩展包。
这就是 greenlet 的由来,因此 greenlet 是底层实现了原生协程的 C扩展库
greenlet的基本使用
# 基于greenlet的生产者消费者协程 from greenlet import greenlet import random import time def Producer(): while True: item = random.randint(1, 10) print("生产<{}>中...".format(item)) time.sleep(1) c.switch(item) # 切换到消费者,并将item传入。 def Consumer(): while True: item = p.switch() # 切换到生产者。等待生产者传递参数item print("消费<{}>中..".format(item)) c = greenlet(Consumer) # 将普通函数编程协程 p = greenlet(Producer) # 同理 c.switch() # 启动协程,Consumer先执行 """ 从consumer开始执行,执行到item=p.switch()时,程序切换到producer,并等待传参 producer得到执行权后,生成一个item,并往下执行代码 当producer执行到c.switch(item)时,程序携带传递的item切换到consumer, consumer继续往下执行,直到下一次运行到p.switch时,交出执行权,切换到producer,重复以上过程 greenlet的价值在于高性能的原生协程, 且语义更加明确、显示切换 执行到switch时就切换程序 直接将函数包装成协程,可以保留原代码的风格 """
什么是gevent?
虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。
但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。
帮我们做好封装,允许我们以类似于线程的方式使用协程。以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。
gevent的常用操作
""" gevent: 通过greenlet实现协程,核心就是遇到IO操作,会自动切换到其他协程 """ # 将python标准库中的一些阻塞操作变为非阻塞 from gevent import monkey;monkey.patch_all() # 使用猴子补丁要写在第一行 import gevent def test1(): print("test1") gevent.sleep(0) # 模拟耗时操作 print("test11") def test2(): print("test2") gevent.sleep(0) # 模拟耗时操作 print("test22") # g1 = gevent.spawn(test1) # 将函数封装成协程,并启动 # g2 = gevent.spawn(test2) # gevent.joinall([g1, g2]) """ # joinall() 阻塞当前流程,执行给定的greenlet(列表中的对象),等待程序执行完 # spawn是启动协程,参数为函数名及其参数 运行结果: test1 test2 test11 test22 代码执行test1,打印test1,遇到gevent.sleep(0)时切换程序,执行test2 test()执行,打印test2,执行到gevent.sleep(0)时切换程序 执行test1在gevent.sleep(0)后面的代码,直到再次遇到gevent时,切换程序 然后在test2中,继续执行gevent后的代码,直到遇到gevent时,再次切换 直到程序执行完毕 gevent的价值在于它的使用基于epoll的libev来避开阻塞; 使用基于gevent的高效协程,来切换执行 只在遇到阻塞的时候切换,没有轮询和线程开销 """
基于gevent的并发服务器
# 基于gevent的并发服务器实现 import gevent # 将python内置的socket换成封装了IO多路复用的socket from gevent import monkey;monkey.patch_all() import socket # 实例化socket server = socket.socket() # 绑定ip和端口 server.bind(('0.0.0.0', 8000)) # 绑定监听数量 server.listen(1000) def worker(connection): """ 协程需要处理的事情 :param connection: :return: """ while True: recv_data = connection.recv(1024) # 等待接收数据 if recv_data: print(recv_data) connection.send(recv_data) # 将接收的数据原路返回 else: connection.close() # 发送完毕断开 break while True: conn, addr = server.accept() # 等待客户端连接,遇到阻塞切换 gevent.spawn(worker, conn) # 生成协程,并将conn作为参数传入
gevent的通信
# gevent通信 import time import random from gevent import monkey;monkey.patch_all() import gevent from gevent.queue import Queue def write(q): while True: print("put:{}".format('text')) q.put('text') gevent.sleep(0) # 模拟阻塞 def read(q): while True: print("get:{}".format(q.get())) # get本身是阻塞 # q = Queue() # w = gevent.spawn(write, q) # 遇到阻塞自动切换 # r = gevent.spawn(read, q) # gevent.joinall([w, r])