greenlet 官方文档:https://greenlet.readthedocs.io/en/latest/

From:https://www.jianshu.com/u/3ab212f28d91

Python Gevent – 高性能的 Python 并发框架:https://blog.csdn.net/freeking101/article/details/53097420

在 Python 里,按照官方解释 greenlet 是轻量级的并行编程gevent 就是利用 greenlet 实现的基于协程(coroutine)的 python 的网络 library通过使用 greenlet 提供了一个在 libev 事件循环顶部的高级别并发 API。即 gevent 是对 greenlet 的高级封装。

greenlet

为了更好使用 协程 来完成多任务,python 中 greenlet 模块对其封装,从而使得切换任务变得更加简单

安装方式:pip3 install greenlet

官网示例:

from greenlet import greenletdef test1():print(12)gr2.switch()print(34)def test2():print(56)gr1.switch()print(78)gr1 = greenlet(test1)
gr2 = greenlet(test2)gr1.switch()  # 切换到 gr1 开始运行, 即 从 gr1 对应 的 test1 开始运行

运行代码,输出为:12 56 34

当创建一个 greenlet 时,首先初始化一个空的栈, switch 到这个栈的时候,会运行在 greenlet 构造时传入的函数(首先在test1中打印 12), 如果在这个函数(test1)中 switch 到其他协程(到了test2 打印 56),那么该协程会被挂起,等到切换回来(在test2 中切换到 test1 打印34)。当这个协程对应函数执行完毕,那么这个协程就变成dead状态。

看下面代码:

from greenlet import greenletdef test1():print(12)gr2.switch()print(34)def test2():print(56)gr1.switch()print(78)gr1 = greenlet(test1)
gr2 = greenlet(test2)gr2.switch()  # 切换到 gr2 开始运行, 即 从 gr2 对应 的 test2 开始运行

运行代码,输出为:56 12 78

greenlet 的 module 与 class

一起看一下greenlet中的属性:

其中,比较重要的是: getcurrent()类greenlet异常类GreenletExit 
getcurrent() :返回当前的greenlet实例;
GreenletExit:是一个特殊的异常,当触发了这个异常的时候,即使不处理,也不会抛到其parent(后面会提到协程中对返回值或者异常的处理)

然后我们再来看看 greenlet.greenlet 这个类:

比较重要的几个属性:

  1.   run:当 greenlet 启动的时候会调用到这个callable,如果我们需要继承greenlet.greenlet时,需要重写该方法
  2.   switch:前面已经介绍过了,在greenlet之间切换
  3.   parent:可读写属性,后面介绍
  4.   dead:如果greenlet执行结束,那么该属性为true
  5.   throw:切换到指定greenlet后立即跑出异常

注意,本文后面提到的 greenlet 大多都是指 greenlet.greenlet 这个 class,注意区分

Switch not call

对于 greenlet,最常用的写法是 x = gr.switch(y)。 这句话的意思是切换到 gr,传入参数 y。当从其他协程(不一定是这个gr)切换回来的时候,将值付给 x 。

import greenletdef test1(x, y):z = gr2.switch(x+y)print('test1 ', z)def test2(u):print('test2 ', u)gr1.switch(10)gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print(gr1.switch("hello", " world"))

输出:
    'test2 ' 'hello world'
    'test1 ' 10
    None
上面的例子,第12行从 main greenlet 切换到了gr1,test1 第3行切换到了gr2,然后 gr1 挂起,第8行从 gr2 切回 gr1 时,将值(10)返回值给了 z。

每一个 Greenlet 都有一个 parent,一个新的 greenlet 在哪里创生,当前环境的 greenlet 就是这个新 greenlet 的 parent。所有的greenlet 构成一棵树,其跟节点就是还没有手动创建 greenlet 时候的 ”main” greenlet(事实上,在首次 import greenlet 的时候实例化)。当一个协程 正常结束,执行流程回到其对应的parent;或者在一个协程中抛出未被捕获的异常,该异常也是传递到其parent。

学习 python的时候,有一句话会被无数次重复 ”everything is oblect”,一切皆对象。
在学习 greenlet 的调用中,同样有一句话应该深刻理解, “switch not call”。切换 不是 调用。

import greenletdef test1(x, y):print(id(greenlet.getcurrent()), id(greenlet.getcurrent().parent))  # 40240272 40239952z = gr2.switch(x+y)print('back z', z)def test2(u):print(id(greenlet.getcurrent()), id(greenlet.getcurrent().parent))  # 40240352 40239952return 'hehe'gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print(id(greenlet.getcurrent()), id(gr1), id(gr2))     # 40239952, 40240272, 40240352
print(gr1.switch("hello", " world"), 'back to main')   # hehe back to main

由这个例子可以看出,尽管是从 test1 所在的协程 gr1 切换到了 gr2,但 gr2 的 parent 还是 ’main’ greenlet,因为默认的 parent取决于 greenlet 的创生环境。另外,在 test2 中 return 之后整个返回值返回到了其 parent,而不是 switch 到该协程的地方(即不是 test1 ),这个跟我们平时的函数调用不一样,记住 “switch not call” 。对于异常,也是展开至 parent:

import greenletdef test1(x, y):try:z = gr2.switch(x+y)except Exception:print('catch Exception in test1')def test2(u):assert Falsegr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
try:gr1.switch("hello", " world")
except:print('catch Exception in main')

输出为:
   catch Exception in main

greenlet 生命周期

本文开始的地方提到第一个例子中的 gr2 其实并没有正常结束,我们可以用 greenlet.dead 这个属性来查看:

from greenlet import greenletdef test1():gr2.switch(1)print('test1 finished')def test2(x):print('test2 first', x)z = gr1.switch()print('test2 back', z)gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
print('gr1 is dead?: %s, gr2 is dead?: %s' % (gr1.dead, gr2.dead))
gr2.switch()
print('gr1 is dead?: %s, gr2 is dead?: %s' % (gr1.dead, gr2.dead))
print(gr2.switch(10))

输出如下:

test2 first 1
test1 finished
gr1 is dead?: True, gr2 is dead?: False
test2 back ()
gr1 is dead?: True, gr2 is dead?: True
10

从这个例子可以看出:
1.只有当协程对应的函数执行完毕,协程才会 die,所以第一次 Check 的时候 gr2 并没有 die,因为第 9 行切换出去了就没切回来。在 main 中再 switch 到 gr2 的时候, 执行后面的逻辑,gr2 die
2.如果试图再次 switch 到一个已经是 dead 状态的 greenlet 会怎么样呢,事实上会切换到其 parent greenlet。

Greenlet Traceing

Greenlet 也提供了接口使得程序员可以监控 greenlet 的整个调度流程。主要是 gettrace 和 settrace(callback) 函数。

import greenletdef test_greenlet_tracing():def callback(event, args):print(event, 'from', id(args[0]), 'to', id(args[1]))def dummy():g2.switch()def dummyexception():raise Exception('excep in coroutine')main = greenlet.getcurrent()g1 = greenlet.greenlet(dummy)g2 = greenlet.greenlet(dummyexception)print('main id %s, gr1 id %s, gr2 id %s' % (id(main), id(g1), id(g2)))oldtrace = greenlet.settrace(callback)try:g1.switch()except BaseException as e:print('Exception : ', e)finally:greenlet.settrace(oldtrace)test_greenlet_tracing()

结果:

main id 1397838280136, gr1 id 1397838280312, gr2 id 1397838280488
switch from 1397838280136 to 1397838280312
switch from 1397838280312 to 1397838280488
throw from 1397838280488 to 1397838280136
Exception :  excep in coroutine

其中 callback 函数 event 是 switch 或者 throw 之一,表明是正常调度还是异常跑出;args 是二元组,表示是从协程 args[0] 切换到了协程 args[1]。上面的输出展示了切换流程:从 main 到 gr1,然后到 gr2,最后回到 main。

greenlet使用建议

使用greenlet需要注意一下三点:

  • 1. greenlet 创建之后,一定要结束,不能 switch 出去就不回来了,否则容易造成内存泄露
  • 2. python 中每个线程都有自己的 main greenlet 及其对应的 sub-greenlet ,不同线程之间的 greenlet 是不能相互切换的
  • 3. 不能存在循环引用,这个是官方文档明确说明:”Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet’s frames will not be detected. “

来看一个例子:

from greenlet import greenlet, GreenletExithuge = []def show_leak():def test1():gr2.switch()def test2():huge.extend([x * x for x in range(100)])gr1.switch()print('finish switch del huge')del huge[:]gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch()gr1 = gr2 = Noneprint('length of huge is zero ? %s' % len(huge))if __name__ == '__main__':show_leak()# output: length of huge is zero ? 100

在test2函数中,第11行,我们将huge清空,然后再第16行将gr1、gr2的引用计数降到了0。但运行结果告诉我们,第11行并没有执行,所以如果一个协程没有正常结束是很危险的,往往不符合程序员的预期。greenlet提供了解决这个问题的办法,官网文档提到:如果一个greenlet实例的引用计数变成0,那么会在上次挂起的地方抛出GreenletExit异常,这就使得我们可以通过try ... finally 处理资源泄露的情况。如下面的代码:

from greenlet import greenlet, GreenletExithuge = []def show_leak():def test1():gr2.switch()def test2():huge.extend([x * x for x in range(100)])try:gr1.switch()finally:print('finish switch del huge')del huge[:]gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch()gr1 = gr2 = Noneprint('length of huge is zero ? %s' % len(huge))if __name__ == '__main__':show_leak()# output :
# finish switch del huge
# length of huge is zero ? 0

上述代码的switch流程:main greenlet --> gr1 --> gr2 --> gr1 --> main greenlet, 很明显gr2没有正常结束(在第10行挂起了)。第18行之后gr1,gr2的引用计数都变成0,那么会在第10行抛出GreenletExit异常,因此finally语句有机会执行。同时,在文章开始介绍Greenlet module的时候也提到了,GreenletExit这个异常并不会抛出到parent,所以main greenlet也不会出异常。

看上去貌似解决了问题,但这对程序员要求太高了,百密一疏。所以最好的办法还是保证协程的正常结束。

gevent

安装:pip3 install gevent 

因为 python 线程的性能问题,在python中使用多线程运行代码经常不能达到预期的效果。而有些时候我们的逻辑中又需要开更高的并发,或者简单的说,就是让我们的代码跑的更快,在同样时间内执行更多的有效逻辑、减少无用的等待。gevent 就是一个现在很火、支持也很全面的 python第三方协程库。

Python 通过 yield 提供了对 协程 的基本支持,但是不完全。greenlet 已经实现了协程,但是这个需要工人切换,有点麻烦。python 还有一个比 greenlet 更强大的并且能够自动切换任务的模块 gevent,gevent 为 Python提供了比较完善的协程支持。

gevent 是 Python 的第三方并发框架库,以微线程greenlet为核心,使用了epoll事件监听机制以及诸多其他优化而变得高效。而且其中有个monkey类,将现有基于Python 线程直接转化为 greenlet(类似于打patch),通过 greenlet 实现协程,其基本思想是:当一个 greenlet 遇到IO操作时,比如访问网络,就自动切换到其他的 greenlet,等到 IO 操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了 gevent 为我们自动切换协程,就保证总有 greenlet 在运行,而不是等待IO。同时也因为只有一个线程在执行,会极大的减少上下文切换的成本。

gevent 基本使用

示例代码:

from gevent import monkey; monkey.patch_socket()
import geventdef f(n):for i in range(n):print(gevent.getcurrent(), i)g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

运行结果:

<Greenlet at 0x10e49f550: f(5)> 0
<Greenlet at 0x10e49f550: f(5)> 1
<Greenlet at 0x10e49f550: f(5)> 2
<Greenlet at 0x10e49f550: f(5)> 3
<Greenlet at 0x10e49f550: f(5)> 4
<Greenlet at 0x10e49f910: f(5)> 0
<Greenlet at 0x10e49f910: f(5)> 1
<Greenlet at 0x10e49f910: f(5)> 2
<Greenlet at 0x10e49f910: f(5)> 3
<Greenlet at 0x10e49f910: f(5)> 4
<Greenlet at 0x10e49f4b0: f(5)> 0
<Greenlet at 0x10e49f4b0: f(5)> 1
<Greenlet at 0x10e49f4b0: f(5)> 2
<Greenlet at 0x10e49f4b0: f(5)> 3
<Greenlet at 0x10e49f4b0: f(5)> 4

可以看到,3个 greenlet 是依次运行而不是交替运行。

要让 greenlet 交替运行,可以通过 gevent.sleep()交出控制权:

def f(n):for i in range(n):print(gevent.getcurrent(), i)gevent.sleep(0)

执行结果:

<Greenlet at 0x10cd58550: f(5)> 0
<Greenlet at 0x10cd58910: f(5)> 0
<Greenlet at 0x10cd584b0: f(5)> 0
<Greenlet at 0x10cd58550: f(5)> 1
<Greenlet at 0x10cd584b0: f(5)> 1
<Greenlet at 0x10cd58910: f(5)> 1
<Greenlet at 0x10cd58550: f(5)> 2
<Greenlet at 0x10cd58910: f(5)> 2
<Greenlet at 0x10cd584b0: f(5)> 2
<Greenlet at 0x10cd58550: f(5)> 3
<Greenlet at 0x10cd584b0: f(5)> 3
<Greenlet at 0x10cd58910: f(5)> 3
<Greenlet at 0x10cd58550: f(5)> 4
<Greenlet at 0x10cd58910: f(5)> 4
<Greenlet at 0x10cd584b0: f(5)> 4

3个 greenlet 交替运行,把循环次数改为 500000,运行时间长一点,然后在操作系统的进程管理器中看,线程数只有1个。

示例代码:

# -*- coding: utf-8 -*-import geventdef f1():for i in range(5):print('run func: f1, index: %s ' % i)gevent.sleep(0)def f2():for i in range(5):print('run func: f2, index: %s ' % i)gevent.sleep(0)t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])

运行后输出如下图所示:

由图中可以看出,f1 和 f2 是交叉打印信息的,因为在代码执行的过程中,我们人为使用 gevent.sleep(0) 创建了一个阻塞,gevent 在运行到这里时就会自动切换函数切换函数。也可以在执行的时候 sleep 更长时间,可以发现两个函数基本是同时运行然后各自等待。

在实际运用的过程中,我们如果有需要通过人为 sleep 来增加时间间隔或者确保部分逻辑安全的时候,此处使用就很方便了。当然,更多时候我们还是在需要进行网络请求的时候使用 gevent,由于切换是在 IO 操作时自动完成,所以 gevent 需要修改 Python 自带的一些标准库,这一过程在启动时通过 monkey patch 完成

# -*- coding: utf-8 -*-from gevent import monkey; monkey.patch_all()
import gevent
import requests
from datetime import datetimedef f(url):print(f'time: {datetime.now()}, GET: {url}')resp = requests.get(url)print(f'time: {datetime.now()}, {len(resp.text)} bytes received from {url}.')gevent.joinall([gevent.spawn(f, 'https://www.python.org/'),gevent.spawn(f, 'https://www.yahoo.com/'),gevent.spawn(f, 'https://github.com/'),
])

运行上述代码,结果如下:

由上图可以看出,程序基本在同一时间触发了对三个网站的请求,然后各自进行,分别结束,而且结束顺序不同,也就是当 gevent 发现阻塞之后,让当前继续执行,然后自动切换到了另外的请求中运行。而且程序只有一个线程

示例代码:

from gevent import monkey# 有耗时操作时需要
monkey.patch_all()  # 将程序中用到的耗时操作的代码,换为 gevent中自己实现的模块import gevent
import random
import timedef coroutine_work(coroutine_name):for i in range(10):print(coroutine_name, i)time.sleep(random.random())gevent.joinall([gevent.spawn(coroutine_work, "work1"),gevent.spawn(coroutine_work, "work2")]
)

加锁

如果需要在使用 gevent 的时候加锁,也是非常方便的:

# -*- coding: utf-8 -*-import gevent
from gevent.lock import Semaphoresem = Semaphore(1)def f1():for i in range(5):sem.acquire()print('run f1, this is ', i)sem.release()gevent.sleep(1)def f2():for i in range(5):sem.acquire()print('run f2, that is ', i)sem.release()gevent.sleep(0.3)t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])

运行结果如下:

由输出可以发现,程序会同时判断是否在 sleep 以及是否有锁两种情况,然后执行当前的最有操作。

gevent 实现 生产者 - 消费者

# -*- coding: utf-8 -*-from gevent import monkey# 猴子补丁,all是所有能切换协程的地方都切换,包含了socket,所以一般都用all
monkey.patch_all()
from gevent.queue import Queue  # 队列 gevent中的队列
import gevent
import randomqq = Queue(3)def producer(index=1):while True:print(f'producer [{index}] --> ', end='')item = random.randint(0, 99)qq.put(item)print("生产了:", item)def consumer(index=1):while True:print(f'consumer [{index}] --> ', end='')item = qq.get()print("消费了:", item)def main_1():thread_1 = gevent.spawn(producer)thread_2 = gevent.spawn(consumer)thread_3 = gevent.spawn(consumer, 2)thread_list = [thread_1, thread_2, thread_3]gevent.joinall(thread_list)if __name__ == '__main__':main_1()# main_2()pass

gevent 调度过程解析

https://www.jianshu.com/p/f55148c41f54

gevent 比较重要的模块

https://www.jianshu.com/p/0a0feb3fe361

gevent 比较重要的模块,包括Timeout,Event/AsynResult,Semphore,socket patch,这些模块都涉及当前协程与 hub 的切换。

Python 的协程库 greenlet 和 gevent相关推荐

  1. python中的协程:greenlet和gevent

    协程是一中多任务实现方式,它不需要多个进程或线程就可以实现多任务. 1.通过yield实现协程: 代码: import time def A():while 1:print('------A----- ...

  2. python异步协程库_Python连载42-异步协程函数

    一.  asyncio 1.python3.4开始引入标准库之中,内置对异步io的支持 2.asyncio本身是一个消息循环 3.步骤: (1)创建消息循环 (2)把协程导入 (3)关闭 4.举例: ...

  3. python线程协程进程的区别_进程和线程、协程的区别

    现在多进程多线程已经是老生常谈了,协程也在最近几年流行起来.python中有协程库gevent,py web框架tornado中也用了gevent封装好的协程.本文主要介绍进程.线程和协程三者之间的区 ...

  4. python 协程库_python --- 协程编程(第三方库gevent的使用)

    1. 什么是协程? 协程(coroutine),又称微线程.协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制.一个线程可以包含多个协程,对于 ...

  5. python协程库_python中协程的详解(附示例)

    本篇文章给大家带来的内容是关于python中协程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 协程,又称微线程,纤程.英文名Coroutine 协程看上去也是子程序 ...

  6. 协程:Greenlet模块、Gevent模块

    三.Greenlet模块 Greenlet是python的一个C扩展,来源于Stackless python,旨在提供可自行调度的'微线程', 即协程.generator实现的协程在yield val ...

  7. Python Asyncio 所有异步协程库用法详解

    title: Asyncio并发编程 copyright: true top: 0 date: 2019-04-03 14:09:24 tags: Asyncio categories: Python ...

  8. Python学习---协程 1226

    协程[是一个单线程],又称微线程,纤程.英文名Coroutine. 一句话说明什么是协程:协程是一种用户态的轻量级线程[程序员自己去切换线程] 协程条件: 必须在只有一个单线程里实现并发 修改共享数据 ...

  9. python中协程的理解_python协程的理解

    一.介绍 什么是并发? 并发的本质就是切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制): 1.任务发生阻塞 2.计算任务时间过长,需要让出cpu给高 ...

最新文章

  1. 构建私有云时,需要考虑的十大要点
  2. Linux常用的基本命令01
  3. Java泛型解析(02):通配符限定
  4. 风靡全球的15则数学动图:让你秒懂数学概念,看后觉得相见恨晚啊
  5. 贾跃亭旗下FF公司遭做空 美国机构质疑其造车能力:卖不出一辆FF91
  6. mysql维护分区脚本_MySQL 分区表脚本
  7. python 遍历对象_Python遍历对象属性
  8. sql统计表中各类型金额_各类型模具设计中“脱料结构”种类、使用范围揭秘,值得收藏...
  9. OpenCV入门学习笔记之常用的图像处理操作
  10. C语言 商业贷款计算器 等额本金 等额本息
  11. Excel转PDF 一百行代码超简单
  12. hyperf中的缓存之CacheEvict
  13. 图书管理系统(图书类)
  14. quartz mysql 表 集群配置_Spring整合实战丨Quartz 集群配置
  15. 微信公众平台 html,微信公众平台.html
  16. 电容或电感的电压_如何通俗的理解电流,电压,电阻,电容和电感电工技术知识学习干货分享...
  17. 基于Umi的移动端框架Alita-v2.6.7源码
  18. android文字闪动效果
  19. CentOS6云服务器磁盘扩容方案
  20. vulnhub靶机-FIRSTBLOOD: 1

热门文章

  1. CUDA层硬件debug之路
  2. 深度学习可以与大数据分手吗?
  3. 论文浅尝 - TACL2020 | TYDI QA:Google 发表一个多语言的问答语料库
  4. 论文浅尝 | 基于知识图的问答变分推理
  5. pytorch 和 tensorflow2.0 方法替换
  6. Linux下的Tomcat服务器修改server.xml中的8080端口号后出现不能访问首页
  7. 契约测试:解决微服务测试的问题
  8. 单链表的头插法和尾插法c语言实现
  9. 全球知名大学课件下载地址汇总
  10. tomcat server.xml配置详解