这两天没有更新博客,但是学习依然在进行中,fighting!!
今日学习关键词:协程,greenlet,gevent
首先讲到了协程,协程是什么?
我的理解,协程也是一种多任务的实现方式,但他和多线程多进程不同的是,他是通过函数间的切换来实现并发的。
协程的效率:因为协程是在单个线程或进程内部执行,不需要保存各种信息(PID等),也不需要单独去占用cpu,所以可以在某些方面协程的效率要更高。
那么用python如何去实现协程呢,很简单,协程的原理就是生成器(generator),看下面代码:
import time
def test1():
while True:
print("----A----")
yield
time.sleep(0.5)
def test2(c):
while True:
print("----B----")
c.__next__()
time.sleep(0.5)
if __name__ == "__main__":
A = test1()
test2(A)
运行结果:
----B----
----A----
----B----
----A----
----B----
----A----
----B----
----A----
----B----
----A----
...
首先创建一个生成器对象A,然后调用test2函数,执行到c.__next__()时会跳到test1,而后执行到yield时会继续test2,以此类推
但是由于这个版本并不是那么的好看(毕竟我们用的是python,提升逼格用模块),我们引入greenlet模块
from greenlet import greenlet
import time
def test1():
while True:
print("----A----")
pr2.switch()
time.sleep(0.5)
def test2():
while True:
print("----B----")
pr1.switch()
time.sleep(0.5)
if __name__ == "__main__":
pr1 = greenlet(test1)
pr2 = greenlet(test2)
pr1.switch()
运行结果:
----A----
----B----
----A----
----B----
----A----
----B----
----A----
----B----
----A----
...
创建两个greenlet对象,然后先从pr1开始执行,两个函数之间进行切换。
接下来就是更高逼格的东西,引入了gevent模块
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
gv1 = gevent.spawn(f, 5)
gv2 = gevent.spawn(f, 5)
gv3 = gevent.spawn(f, 5)
gv1.join()
gv2.join()
gv3.join()
运行结果:
<Greenlet "Greenlet-0" at 0x7fdaafe38b48: f(5)> 0
<Greenlet "Greenlet-0" at 0x7fdaafe38b48: f(5)> 1
<Greenlet "Greenlet-0" at 0x7fdaafe38b48: f(5)> 2
<Greenlet "Greenlet-0" at 0x7fdaafe38b48: f(5)> 3
<Greenlet "Greenlet-0" at 0x7fdaafe38b48: f(5)> 4
<Greenlet "Greenlet-1" at 0x7fdaafe38e48: f(5)> 0
<Greenlet "Greenlet-1" at 0x7fdaafe38e48: f(5)> 1
<Greenlet "Greenlet-1" at 0x7fdaafe38e48: f(5)> 2
<Greenlet "Greenlet-1" at 0x7fdaafe38e48: f(5)> 3
<Greenlet "Greenlet-1" at 0x7fdaafe38e48: f(5)> 4
<Greenlet "Greenlet-2" at 0x7fdaaeb8b048: f(5)> 0
<Greenlet "Greenlet-2" at 0x7fdaaeb8b048: f(5)> 1
<Greenlet "Greenlet-2" at 0x7fdaaeb8b048: f(5)> 2
<Greenlet "Greenlet-2" at 0x7fdaaeb8b048: f(5)> 3
<Greenlet "Greenlet-2" at 0x7fdaaeb8b048: f(5)> 4
WTF?为什么是一个一个执行的?
其实在gevent模块当中有一个机制,它会自动判断是否有延时操作,如果有延时操作它才会在函数之间进行切换,所以我们加入延时操作之后再看结果:
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
gv1 = gevent.spawn(f, 5)
gv2 = gevent.spawn(f, 5)
gv3 = gevent.spawn(f, 5)
gv1.join()
gv2.join()
gv3.join()
运行结果:
<Greenlet "Greenlet-0" at 0x7f84a1deeb48: f(5)> 0
<Greenlet "Greenlet-1" at 0x7f84a1deee48: f(5)> 0
<Greenlet "Greenlet-2" at 0x7f84a0b41048: f(5)> 0
<Greenlet "Greenlet-0" at 0x7f84a1deeb48: f(5)> 1
<Greenlet "Greenlet-1" at 0x7f84a1deee48: f(5)> 1
<Greenlet "Greenlet-2" at 0x7f84a0b41048: f(5)> 1
<Greenlet "Greenlet-0" at 0x7f84a1deeb48: f(5)> 2
<Greenlet "Greenlet-1" at 0x7f84a1deee48: f(5)> 2
<Greenlet "Greenlet-2" at 0x7f84a0b41048: f(5)> 2
<Greenlet "Greenlet-0" at 0x7f84a1deeb48: f(5)> 3
<Greenlet "Greenlet-1" at 0x7f84a1deee48: f(5)> 3
<Greenlet "Greenlet-2" at 0x7f84a0b41048: f(5)> 3
<Greenlet "Greenlet-0" at 0x7f84a1deeb48: f(5)> 4
<Greenlet "Greenlet-1" at 0x7f84a1deee48: f(5)> 4
<Greenlet "Greenlet-2" at 0x7f84a0b41048: f(5)> 4
这下解决了,但是要注意sleep是gevent模块的而不是time模块的。其实在gevent模块当中包含了一些其他带有延时功能模块的方法,接下来我们利用gevent来搭建一个tcp服务器:
import gevent
from gevent import socket, monkey
#这句话必须执行,会自动更改代码
monkey.patch_all()
def handle_request(conn):
while True:
data = conn.recv(1024)
if not data:
conn.close()
break
print("recv", data)
conn.send(data)
def server(port):
s = socket.socket()
s.bind(("", port))
s.listen(5)
while True:
cli, Addr = s.accept()
gevent.spawn(handle_request, cli)
if __name__ == "__main__":
server(7788)
这个代码就是老师课件上的代码,可以发现在gevent中也有socket。原理和之前学习的TCP一样,在这里不过多记录,但是要注意,必须在代码前加上那句monkey.patch_all(),否则会出现错误。