Python中的协程(yield)

版权声明:分享才能获得最大的价值 https://blog.csdn.net/qq_32252957/article/details/84261621

Python语言是支持协程的,下面我们简单介绍一下,并且举几个例子

1.什么是协程(Coroutine)?

协程又称为微线程,协程的完成主要靠yield关键字,协程执行过程中,在子程序内部可中断,然后

转而执行别的子程序,在适当的时候再返回来接着执行。

2.协程的优势?

(1) 我们知道在并发中使用多线程线程进行任务切换需要上下文的切换,需要一些开销,甚至有些时候

单核中使用多线程的效率没有单线程效率高(线程对象的创建,以及上下文的切换消耗性能占主导

作用),解决这个问题的方法之一就是使用协程。

(2) Python的多线程很鸡肋(由于存在GIL全局解释器锁,因此主张在计算密集型使用多进程,IO密集型

扫描二维码关注公众号,回复: 4529627 查看本文章

推荐使用多线程),我们可以使用多进程+协程的方式,即充分利用多核,又充分发挥协程的高效率,

可获得较好的性能。

(3) 不需要多线程的锁机制,当然也可以使用一些原子类(CAS算法), 因为协程只有一个线程,

也不存在同时写变量冲突,在协程中控制共享资源不需要加锁,只需要判断状态就好了。

3.Python中的协程

Python对协程的支持是通过generator实现的

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Python的yield不但可以返回一个值,它还可以接受调用者发出的参数。

1.带有yield关键字的函数自动变成生成器

2.生成器被调用时不会立即执行

3.对于生成器,当调用函数next(generator)时,将获得生成器yield后面表达式的值;

4.当生成器已经执行完毕时,再次调用next函数,生成器会抛出StopIteration异常

下面一个简单的例子:

# -*- coding: utf-8 -*-
import time
from inspect import isgeneratorfunction
import types
from collections import Iterable

def run1():
	while True:
		print("Tell me to your heart")
		time.sleep(1)
		yield 1

def run2():
	while True:
		print("Tell me to your soul")
		time.sleep(1)
		yield 2

if __name__ == "__main__":
	# 每次调用runx()函数都会生成一个新的generator实例,各实例互不影响
	t1 = run1()
	t2 = run2()
	print(isgeneratorfunction(run1)) # runx()函数是一个generator函数
	print(isinstance(run1, types.GeneratorType)) 
	print(isinstance(t1, types.GeneratorType)) # t1是调用run1()返回的一个generator实例
	print(isinstance(run1, Iterable))
	print(isinstance(t2, Iterable)) # t2是可迭代的
	help(t1)
	while True:
		print(next(t1))
		print(next(t2))

"""
result:
True
False
True
False
True
Help on generator object:

run1 = class generator(object)
 |  Methods defined here:
 |  
 |  __del__(...)
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  gi_code
 |  
 |  gi_frame
 |  
 |  gi_running
 |  
 |  gi_yieldfrom
 |      object being iterated by yield from, or None

Tell me to your heart
1
Tell me to your soul
2
Tell me to your heart
1
Tell me to your soul
2
Tell me to your heart
1
"""

可以看到t1,t2对象的一些来自于迭代器的魔法方法。

之前在Java多线程专栏中我们已经实战过生产者-消费者模型,使用的是Java的notify/wait机制,

现在改用Python中的协程(Java中也有的,如Kilim、quasar 、PicoThreads框架,以后并发专栏也会讲解的;

只不过Java语言级别原生不支持协程。这里对并发感兴趣的可以去看看go,对于高并发支持真的是棒,

我也刚开始接触go),生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回

生产者继续生产,效率极高。

# -*- coding: utf-8 -*-

def consumer():
	r = ''
	while True:
		n = yield r 
		if not n:
			return
		print("[CONSUMER] Consuming %s..." % n)
		r = "200 OK"

def produce(c):
	c.send(None)
	n = 0
	while n < 5:
		n = n + 1
		print("[PRODUCER] Producint %s..." % n)
		r = c.send(n)
		print("[PRODUCER] Consumer return: %s" % r)
	c.close()

c = consumer()
produce(c)

"""
result:
[PRODUCER] Producint 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
[Finished in 0.6s]
"""

注意到consumer函数是一个generator,把一个consumer传入produce后:

  1. 首先调用c.send(None)启动生成器;

  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

  4. produce拿到consumer处理的结果,继续生产下一条消息;

  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produceconsumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:

“子程序就是协程的一种特例。”

猜你喜欢

转载自blog.csdn.net/qq_32252957/article/details/84261621