今天早上偶然看到一篇文章《PHP如何实现协程》,顿时惊呆了!PHP什么时候这么强了?那我还写Go 做什么?仔细阅读文章发现php使用的是yield关键实现的,心想这个不就是py里的生成器吗?虽然我不知道PHP里的yield和py的有什么区别,由于之前并没有深入了解过着玩意所以借机会了解一下!
我们都知道函数(子例程)的控制权要等到return 后才会交给调用者,函数中的变量随着控制权结束后也就消失了,需要等到下次调用重新开始,然而yield 就是这么神奇可以执行到yield 关键字处然后将控制权交给调用者,然后在次进入函数从上次结束的地方继续执行不断循环
先从一个小需求入手:
求出一个数字列表中的素数
import math
def get_primes(input_list):return (element for element in input_list if is_prime(element))def is_prime(number):if number > 1:if number == 2:return Trueif number % 2 == 0:return Falsefor current in range(3,int(math.sqrt(number)+1),2):if number % current == 0:return Falsereturn Truereturn False
nums = [1,2,3,4,5,6,7,8,9]
list = get_primes(nums)
for i in list:print(i)
这个需求很简单就实现了~~·
假如现在万恶的产品经理想要求无限大的素数
那么你可能会想我直接从一个非常的大的数开始算不行么?可以的!但是就是有点LOW
如果我们往list存无限个数字 就更加不可能了
这是我们的yield就非常有作用了,上面说了遇到yield 会将交出控制权,然后进入函数继续执行
这里我们改造一下get_primes 函数
def get_primes_v2(number):while True:time.sleep(2)print("*******", number)if is_prime(number):yield numbernumber+=1
使用while True不停的去执行并且每次到yield 关键字处就将结果返回并且保存函数的内部的状
态,实现每次number+1 的效果这就是yield 的奇妙之处
接下来看看如何使用yield 实现协程(来自廖雪峰老师的例子)
import timedef consumer():r = ''while True:n = yield rif not n:returnprint('[CONSUMER] Consuming %s...' % n)time.sleep(1)r = '200 OK'def produce(c):c.__next__()n = 0while n < 5:n = n + 1print('[PRODUCER] Producing %s...' % n)r = c.send(n)print("send.......")print('[PRODUCER] Consumer return: %s' % r)c.close()c = consumer()
produce(c)
注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:
首先调用c.next()启动生成器;
然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
consumer通过yield拿到消息,处理,又通过yield把结果传回;
produce拿到consumer处理的结果,继续生产下一条消息;
produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
其实通过yield 也知道go 中goroutine 翻译成协程还是有很大的不同的,goroutine 和协程是根本上不同的东西,之后再探讨go 中的goroutine 是如何实现的