Python在并发处理上不仅提供了多进程和多线程的处理,还包括了协程。
协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。
与之一概念最为类似的是Python中的yield
class DemoTwo:
@staticmethod
def run():
def gen(max):
n = 0
while n < max:
yield n
n += 1
my_numbers = gen(10)
for number in my_numbers:
print(number)
带有yield的函数,已经不是一个普通的函数,这样的函数会返回一个“生成器”,这个生成器类似于迭代器(所以它可以被for使用),这个生成器也就是上文中的my_numbers有一个next()方法,用于取出下一个数。
表面上,我们可以看做my_numerbs保存了0-9,通过yield我们将这些数保存下来了,在我们进行迭代的时候他才会取出这些数。
实际上,这是一种错觉,如果只是单纯地保存执行结果,那yield看似并没有我们所想的强大。
我们来作一个实验来看看他到底是怎么样的:
class DemoTwo:
@staticmethod
def run():
def gen(max):
n = 0
while n < max:
print('start',n)
yield n
print('end',n)
n += 1
my_numbers = gen(10)
my_numbers.__next__()
my_numbers.__next__()
我们可以看到输出 (DemoTwo.run())
start 0
end 0
start 1
也许到了现在我们可以理解yield了。
yield相当于一个中断,将执行权交给了其他函数,同时将自己的栈上下文保存到了生成器中。
而在调用函数内next()方法则相当于还原了栈上下文并继续执行,直到下一个yield,又发生了一次中断。
再来看下面一段代码:
class DemoTwo:
@staticmethod
def run():
def gen(max):
n = 0
while n < max:
print('start',n)
a = yield n
print(a)
print('end',n)
n += 1
my_numbers = gen(10)
my_numbers.__next__()
my_numbers.send('test')
yield并不仅仅可以传递一个参数,也可以接受一个参数,通过send的方法,我们可以灵活地与yield函数进行交互。
也许next可以看作next(None) (在python3中已经没有next(),取而代之的是__next__(),当然send已经足够了)
yield是一个不完全的协程实现,那么来总结一下:
1. 协程是一种并发的能力
2. 协程是单个线程内的表现
3. 协程通过程序保存上下文、执行等行为
和线程对比:
1. 协程没有线程切换开销
2. 单线程没有锁机制
from gevent import monkey
monkey.patch_socket()
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),
gevent.spawn(waiter),
gevent.spawn(waiter),
gevent.spawn(waiter)
])
if __name__ == '__main__': main()