gevent学习

# gevent 学习
# 协程 称为微线程
#进程与线程的关系很容易理解
#可以把线程与协程的关系类比于进程与线程的关系

'''
因为python线程的性能问题,在python中使用多线程运行代码经常不能达到预期的效果。而有些时候我们的逻辑中又需要开更高的并发,或者简单的说,就是让我们的代码跑的更快,在同样时间内执行更多的有效逻辑、减少无用的等待。gevent就是一个现在很火、支持也很全面的python第三方协程库。
gevent是python的一个并发框架,以微线程greenlet为核心,使用了epoll事件监听机制以及诸多其他优化而变得高效。而且其中有个monkey类,将现有基于Python线程直接转化为greenlet(类似于打patch)。在运行时的具体流程大概就是:
当一个greenlet遇到IO操作时,比如访问网络/睡眠等待,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。同时也因为只有一个线程在执行,会极大的减少上下文切换的成本。

作者:WolfLC
链接:https://www.jianshu.com/p/bb6c7f9aa1ae
来源:简书
'''

#安装gevent
# pip3 install gevent
from datetime import datetime

import gevent

# def f1():
#     for i in range(5):
#         print(f'run func:f1,index :{i}')
#         gevent.sleep(0)
#
# def f2():
#     for i in range(5):
#         print(f'run func:f2,index :{i}')
#         gevent.sleep(0)
#
# t1=gevent.spawn(f1)
# t2=gevent.spawn(f2)
# gevent.joinall([t1,t2])

'''
run func:f1,index :0
run func:f2,index :0
run func:f1,index :1
run func:f2,index :1
run func:f1,index :2
run func:f2,index :2
run func:f1,index :3
run func:f2,index :3
run func:f1,index :4
run func:f2,index :4
'''
# 可以看出f1,f2交叉打印信息,因为每次输出后,我们sleep,形成了一个阻塞,这时候,gevent运行到这儿,就会自动切换函数。



# gevent爬虫测试
# from gevent import monkey; monkey.patch_all()
# import requests
#
# def f(url):
#     print(f'time:{datetime.now()},get url:{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.baidu.com/'),
#     gevent.spawn(f,'https://github.com/'),
# ])
'''
time:2021-02-17 11:29:35.642753,get url:https://www.python.org/
time:2021-02-17 11:29:35.642753,get url:https://www.baidu.com/
time:2021-02-17 11:29:35.652753,get url:https://github.com/
time: 2021-02-17 11:29:35.852759, 2443 bytes received from https://www.baidu.com/
time: 2021-02-17 11:29:36.191394, 200301 bytes received from https://github.com/
time: 2021-02-17 11:29:37.721411, 49604 bytes received from https://www.python.org/

这里要加上from gevent import monkey; monkey.patch_all()
猴子补丁?
不加的话 没有效果
'''

# gevent 协程加锁

from gevent.lock import Semaphore

sem=Semaphore(1)

def f1():
    for i in range(5):
        sem.acquire() #获得锁
        print(f'run func:f1,index :{i}')
        sem.release() #解锁
        gevent.sleep(1)

def f2():
    for i in range(5):
        sem.acquire()  # 获得锁
        print(f'run func:f2,index :{i}')
        sem.release()  # 解锁
        gevent.sleep(1)

t1=gevent.spawn(f1)
t2=gevent.spawn(f2)
gevent.joinall([t1,t2])

'''
run func:f1,index :0
run func:f2,index :0
run func:f1,index :1
run func:f2,index :1
run func:f1,index :2
run func:f2,index :2
run func:f1,index :3
run func:f2,index :3
run func:f1,index :4
run func:f2,index :4

这时候系统判断了锁的状态来执行程序
'''
# greenlet是gevent的基础

'''
Greenlet是python的一个C扩展,旨在提供可自行调度的‘微线程’, 即协程。
generator实现的协程在yield value时只能将value返回给调用者(caller)。
而在greenlet中,target.switch(value)可以切换到指定的协程(target),
然后yield value。greenlet用switch来表示协程的切换,
从一个协程切换到另一个协程需要显式指定。

作者:WolfLC
链接:https://www.jianshu.com/p/1bb68eeec54d
来源:简书
'''

# 初探

# 导包
from greenlet import greenlet

# def test1():
#     print(12)
#     gr2.switch()
#     print(34)
# def test2():
#     print(56)
#     gr1.switch()
#     print(78)
#
# gr1=greenlet(test1)
# gr2=greenlet(test2)
# gr1.switch()

'''
12
56
34

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


# switch函数
# def test1(x,y):
#     z=gr2.switch(x+y)
#     print(f'test1:{z}')
# def test2(u):
#     print(f'test2:{u}')
#     gr1.switch(10)
# gr1=greenlet(test1)
# gr2=greenlet(test2)
# print(gr1.switch("hello", "zxy"))

'''
test2:hellozxy
test1:10
None
'''

'''
每一个Greenlet都有一个parent,一个新的greenlet在哪里创生,当前环境的greenlet就是这个新greenlet的parent。
'''
# import greenlet
# def test1(x, y):
#     print (id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) ) # 2599234693248 2599237049504
#     z = gr2.switch(x+y)
#     print ('back z', z)
#
# def test2(u):
#     print (id(greenlet.getcurrent()), id(greenlet.getcurrent().parent)) # 2599237049680 2599237049504
#     return 'hehe'
#
# gr1 = greenlet.greenlet(test1)
# gr2 = greenlet.greenlet(test2)
# print (id(greenlet.getcurrent()), id(gr1), id(gr2)   ) # 2599237049504 2599234693248 2599237049680
# 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”
'''

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

# catch Exception in main
#从这个例子可以看出   对于异常,也是展开至parent


# greenlet的生命周期

from greenlet import greenlet
def 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

从这个例子可以看出
只有当协程对应函数执行完毕,协程才会die;
所以例子中,第一次执行完,gr2是没有die的,因为在test2中切换到test1后就没切回来过
当再次切换到gr2时,test2中代码执行完,gr2才die
如果试图再次switch到一个已经是dead状态的greenlet会怎么样呢,事实上会切换到其parent greenlet
'''
# 了解了greenlet后,再回来学习一下gevent

#sleep函数
# import gevent
# gevent.sleep()
'''
sleep的作用很简单,触发一个阻塞的操作,导致调用hub.wait,
从当前greenlet.greenlet切换至Hub,超时之后再从hub切换到之前的greenlet继续执行。
通过这个例子可以知道,gevent将任何阻塞性的操作封装成一个Watcher,
然后从调用阻塞操作的协程切换到Hub,等到阻塞操作完成之后,再从Hub切换到之前的协程。

作者:WolfLC
链接:https://www.jianshu.com/p/f55148c41f54

'''
import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0)
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])
'''
Running in foo
Explicit context to bar
Explicit context switch to foo again
Implicit context switch back to bar
'''
#gevent.spawn创建一个新的Greenlet,并注册到hub的loop上,调用gevent.joinall或者Greenlet.join的时候开始切换到hub

import time
import gevent
from gevent.timeout import Timeout

# SLEEP=3
# TIMEOUT=2
#
# timeout=Timeout(TIMEOUT)
# timeout.start()
#
# def wait():
#     gevent.sleep(SLEEP)
#     print("log in wait")
#
# begin=time.time()
# try:
#     gevent.spawn(wait).join()
# except Timeout:
#     print('after %s catch Timeout Exception' % (time.time() - begin))
# finally:
#     timeout.cancel()

# after 2.0007071495056152 catch Timeout Exception

'''
这个类在gevent.timeout模块,其作用是超时后在当前协程抛出异常,这样执行流程也强制回到了当前协程
'''

# SLEEP = 4
# TIMEOUT = 5
#
# timeout = Timeout(TIMEOUT)
# timeout.start()
#
# def wait():
#     gevent.sleep(SLEEP)
#     print('log in wait')
#
# begin = time.time()
# try:
#     gevent.spawn(wait).join()
# except Timeout:
#     print('after %s catch Timeout Exception'  % (time.time() - begin))
# finally:
#     timeout.cancel()
#
# gevent.sleep(2)
# print('program will finish')

'''
一定要记得在finally调用cancel,否则如果协程先于TIMEOUT时间恢复,之后还会抛出异常
Timeout只是切换到当前协程,并不会取消已经注册的协程(上面通过spawn发起的协程)
'''

# SLEEP = 6
# TIMEOUT = 5
#
#
# def wait():
#     gevent.sleep(SLEEP)
#     print('log in wait')
#
#
# begin = time.time()
# try:
#     gevent.spawn(wait).join(TIMEOUT)
# except Timeout:
#     print('after %s catch Timeout Exception' % (time.time() - begin))
#
# print('program will exit', time.time() - begin)
# program will exit 5.047271251678467
'''
gevent对可能导致当前协程挂起的函数都提供了timeout参数,用于在指定时间到达之后恢复被挂起的协程。在函数内部会捕获Timeout异常,并不会抛出
'''

# 同步机制
# 一种是Semaphore 之前用到过 这个是gevent提供的

# Event用来在Greenlet之间同步

import gevent
from gevent.event import Event

'''
Illustrates the use of events
'''


evt = Event()

def setter():
    '''After 3 seconds, wake all threads waiting on the value of evt'''
    print('A: Hey wait for me, I have to do something')
    gevent.sleep(3)
    print("Ok, I'm done")
    evt.set()


def waiter():
    '''After 3 seconds the get call will unblock'''
    print("I'll wait for you")
    evt.wait()  # blocking
    print("It's about time")

def main():
    gevent.joinall([
        gevent.spawn(setter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),

    ])

if __name__ == '__main__': main()

'''
A: Hey wait for me, I have to do something
I'll wait for you
I'll wait for you
Ok, I'm done
It's about time
It's about time

wait等待事件发生,如果事件未发生那么挂起该协程;set通知事件发生,然后hub会唤醒所有wait在该事件的协程
'''

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值