协程

协程(Coroutine

什么是协程,协程又被称为微线程,那么协程是作用在于执行函数 A 的时候, 可以随时中断,然后执行函数 B ,然后又可以中断函数 B 去执行函数 A,也就是说可以进行自由来回切换,但是这个一个过程并不是调用函数,我们要注意一个点,就是这个过程看起来是多线程,但是协程是位于线程里面的,是单线程的

在了解协程之前,先来看看生成器(generator),很多人一听到生成器,第一反应可能就是 yield,那么先用一些案例来看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
"""
可迭代对象(Iterable)
迭代器(Iterator)
生成器(Generator)
"""

def fib(n):
index = 0
a, b = 0, 1
l = []
while index < n:
# l.append(b)
yield b
a, b = b, a+b
index += 1
# return l # 在返回之前 要保留整个列表

start_time = time.time()
# fib(3000000) # 一次全部返回
for i in fib(100000):
pass
print(time.time() - start_time)

通过这个案例可以,可以清楚的知道使用生成器的时间比列表返回快,因为如果使用列表,那么每次返回的时候,必须计算好整个列表的值,然后返回,那么就需要一个比较大的空间用来保存,但是生成器不需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# yield  send  通过yield暂停函数 然后返回数据  如果向函数内部发送数据
def fib(n):
index = 0
a, b = 0,1
l = []
while index < n:
r = yield b # 程序只有运行到这一行才能接受数据
print(r)
a, b = b, a+b
index += 1


f = fib(5)
print(f.send(None))
print(f.send('w'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('消费者 {}'.format(n))
r = 'Ok'

def producer(c):
c.send(None) # 启动生成器 运行到yield 哪一行代码
n = 0
while n<10:
n += 1
r = c.send(n)
print('消费者 返回 {}'.format(r))

c.close()

c = consumer() # 生成了c
producer(c) # 做了函数调用


# 线程和进程 不能自己控制进行 协程可以控制

greenlet 和 gevent 的使用

http://greenlet.readthedocs.io/en/latest/#introduction

greenlet的案例
http://www.bjhee.com/greenlet.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from greenlet import greenlet
import random


def test1():
print(1)
gr2.switch()
print(2)
# gr2.switch() # 如果这个不写 就不能打印4

def test2():
print(3)
gr1.switch()
print(4)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() # 开始运行 水调用了switch 就会切换到哪里去
"""
这里创建了两个greenlet协程对象,gr1和gr2,分别对应于函数test1()和test2()。

使用greenlet对象的switch()方法,即可以切换协程。上例中,我们先调用”gr1.switch()”,函数test1()被执行,

然后打印出”1″;接着由于”gr2.switch()”被调用,协程切换到函数test2(),打印出”3″;之后”gr1.switch()”又被调用,

所以又切换到函数test1()。但注意,由于之前test1()已经执行到第5行,也就是”gr2.switch()”,所以切换回来后会继续往下执行,

也就是打印”2″;现在函数test1()退出,同时程序退出。由于再没有”gr2.switch()”来切换至函数test2(),所以程序”print 4″不会被执行。

switch带参数的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from greenlet import greenlet
import random


def test1():
print(12)
y = gr2.switch(34)
print(y)

def test2(x):
print(x)
gr1.switch(56)
print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() # 开始运行 水调用了switch 就会切换到哪里去
"""
在test1()中调用”gr2.switch()”,由于协程”gr2″之前未被启动,所以传入的参数”56″会被赋在test2()函数的参数”x”上;

在test2()中调用”gr1.switch()”,

由于协程”gr1″之前已执行到第5行”y = gr2.switch(56)”这里,

所以传入的参数”34″会作为”gr2.switch(56)”的返回值,赋给变量”y”。

这样,两个协程之间的互传消息就实现了。
"""

gevent

有了gevent,协程的使用将无比简单,你根本无须像greenlet一样显式的切换,每当一个协程阻塞时,程序将自动调度,gevent处理了所有的底层细节

1
2
3
4
5
6
7
8
9
10
11
12
13
import gevent
def fn1():
print(1)

print(2)

def fn2():
print(3)
print(4)

fn1 = gevent.spawn(fn1)
fn2 = gevent.spawn(fn2)
gevent.joinall([fn1, fn2])

“””
解释下,”gevent.spawn()”方法会创建一个新的greenlet协程对象,
并运行它。”gevent.joinall()”方法会等待所有传入的greenlet协程运行结束后再退出,
这个方法可以接受一个”timeout”参数来设置超时时间,单位是秒。运行上面的程序,
执行顺序如下:
先进入协程test1,打印1
遇到”gevent.sleep(0)”时,test1被阻塞,自动切换到协程test2,打印3
之后test2被阻塞,这时test1阻塞已结束,自动切换回test1,打印2
当test1运行完毕返回后,此时test2阻塞已结束,再自动切换回test2,打印78
所有协程执行完毕,程序退出
“””
greenlet一个协程运行完后,必须显式切换,不然会返回其父协程。而在gevent中,一个协程运行完后,它会自动调度那些未完成的协程

gevent生产者和消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from gevent.queue import Queue
import gevent

queue = Queue(3)

def producer():
while True:
item = random.randint(0, 99)
queue.put(item)
print('生产者 生产了 {}'.format(item))
gevent.sleep(2)


def consumer():
while True:
item = queue.get()
print('消费者 消费了 {}'.format(item))
gevent.sleep(2) # 模拟阻塞


p = gevent.spawn(producer) # spawn启动协程 第二个是参数
c = gevent.spawn(consumer)
gevent.joinall([p, c]) # 阻塞当前流程 执行给定的 greenlet 执行完了 在继续

并发服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from gevent import monkey; monkey.patch_socket()
import gevent
import socket
"""
此后socket标准库中的类和方法都会被替换成非阻塞式的

有些别的库,可能也有这么阻塞 所以 提供了monkey.patch_all()

动态修改
"""


server = socket.socket()
server.bind(('', 8000))
server.listen(1000)


def read(con):
while True:
data = con.recv(1000)
if data:
print(data)
con.send(data)
else:
con.close()
break

print('等待客户端连接..')
while True:
con, addr = server.accept()
print('客户端{}成功连接'.format(addr))
gevent.spawn(read, con)

仅用于个人参考

---------------- The   End ----------------