人生苦短,我用python。

一、协程

协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。

(1)比较专业的理解是:

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

(2)这么来理解协程比较容易:

线程是系统级别的,它们是由操作系统调度;协程是程序级别的,由程序员根据需要自己调度。我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以中断去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。

(3)协程的优点:

  • 无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

(4)协程的缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

二、yield实现协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time

def A():
while True:
print("----A---")
yield
time.sleep(0.5)

def B(c):
while True:
print("----B---")
next(c)
time.sleep(0.5)

if __name__=='__main__':
a = A()
B(a)

三、greenlet模块实现协程

python中的greenlet模块对其封装,从而使得切换任务变的更加简单,greenlet就相当于手动切换,去执行别的子程序,在“别的子程序”中又主动切换回来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.switch() # 切换到gr1中运行

四、gevent模块实现协程

greenlet已经实现了协程,但是这个还要人工切换,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent。

其原理是当一个greenlet遇到IO(指的是input output输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 1、三个greenlet是依次运行而不是交替运行:
import gevent

def 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()


# 2、三个greenlet交替运行:
import gevent

def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
gevent.sleep(1) # 用来模拟一个耗时操作,注意不是time模块中的sleep,内部函数实现io操作

g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()


# 3、gevent并发下载器:
# 实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换
import gevent
from gevent import monkey
from urllib import request

monkey.patch_all() # 有IO操作时需要这一句,把当前程序中的所有io操作都做上标记

def myDownLoad(url):
print('GET: %s' % url)
res = request.urlopen(url)
data = res.read()
print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
gevent.spawn(myDownLoad, 'http://www.baidu.com/'),
gevent.spawn(myDownLoad, 'http://www.itcast.cn/'),
gevent.spawn(myDownLoad, 'http://www.itheima.com/'),
])

持续更新…

最后更新: 2018年12月05日 15:12

原始链接: http://pythonfood.github.io/2017/12/30/python协程/

× 多少都行~
打赏二维码