协程
-
通常在Python中我们进行并发编程一般都是使用多线程或者多进程来实现的,对于计算型任务由于GIL的存在我们通常使用多进程来实现,而对与IO型任务我们可以通过线程调度来让线程在执行IO任务时让出GIL,从而实现表面上的并发。
-
其实对于IO型任务我们还有一种选择就是协程,协程是运行在单线程当中的“并发”,协程相比多线程一大优势就是省去了多线程之间的切换开销,获得了更大的运行效率。Python中的asyncio也是基于协程来进行实现的。在进入asyncio之前我们先来了解一下Python中怎么通过生成器进行协程来实现并发。
yield实现
- 只要在def中有yield关键字的 就称为 生成器
- next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
import time
def demo1():
while True:
print("----111-demo1--")
yield
time.sleep(0.5)
def demo2():
while True:
print("----222-demo2--")
yield
time.sleep(0.5)
def main():
d1 = demo1()
d2 = demo2()
while True:
next(d1)
next(d2)
if __name__ == "__main__":
main()
# output
----111-demo1--
----222-demo2--
----111-demo1--
----222-demo2--
----111-demo1--
----222-demo2--
----111-demo1--
----222-demo2--
...
greenlet实现
from greenlet import greenlet
import time
def test1():
while True:
print "---A--"
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print "---B--"
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到gr1中运行
gr1.switch()
# output
---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--
...
gevent实现
-
greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent
-
其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
-
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
import gevent
def demo(n):
for i in range(n):
print(gevent.getcurrent(), i)
# 这里延迟操作需要调用gevent的sleep
# 若没有I/O操作,gevent不会切换协程
gevent.sleep(0.5)
# spawn 第一个参数是调用的函数名, 第二个参数是传入的参数
g1 = gevent.spawn(demo, 3)
g2 = gevent.spawn(demo, 5)
g3 = gevent.spawn(demo, 2)
g1.join()
g2.join()
g3.join()
# output
<Greenlet "Greenlet-0" at 0x1cabed4f448: demo(3)> 0
<Greenlet "Greenlet-1" at 0x1cabed4f548: demo(5)> 0
<Greenlet "Greenlet-2" at 0x1cabed4f648: demo(2)> 0
<Greenlet "Greenlet-0" at 0x1cabed4f448: demo(3)> 1
<Greenlet "Greenlet-1" at 0x1cabed4f548: demo(5)> 1
<Greenlet "Greenlet-2" at 0x1cabed4f648: demo(2)> 1
<Greenlet "Greenlet-0" at 0x1cabed4f448: demo(3)> 2
<Greenlet "Greenlet-1" at 0x1cabed4f548: demo(5)> 2
<Greenlet "Greenlet-1" at 0x1cabed4f548: demo(5)> 3
<Greenlet "Greenlet-1" at 0x1cabed4f548: demo(5)> 4
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
def 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")
])
- 程序运行结果
work1 0
work2 0
work1 1
work1 2
work1 3
work2 1
work1 4
work2 2
work1 5
work2 3
work1 6
work1 7
work1 8
work2 4
work2 5
work1 9
work2 6
work2 7
work2 8
work2 9