这两天在进行node的编码工作,使用node的awiat异步编程是真滴爽,在py3.5之后也可以使用await关键字后,于是对于python的异步编程这一块又开始兴趣浓厚起来。
一、
协程
说起py的异步编程,就要先谈起协程这一概念。
协程在py3.4被正式引入,其中和它一样重要的概念是事件循环
总所周知,线程是比进程要轻量级的,这里,协程更比线程还要轻量级。 另外,py的多线程是坑爹的,不同于静态语言java,c#这些的多线程,py的多线程的性能要差的多,因为从根源来说,py的多线程其实是假的,只是线程工作切换很快,其实都是单线程在工作。 还有,py在linux的环境下,创建线程损耗的资源是与进程相差无几的。 协程的好处不仅是避免了线程的死锁,变量的统一性的问题,更是少了线程切换的开销。 所以在需要线程数越多的情况下,使用协程来异步编程,更加方便快捷,环保节约。
二、
yield
使用yield关键字创建的生成器在被一个外部生成器调用的时候,它就是一个协程。
概述:yield有一半return的功能,它将相应的值返回给调用next()或者send()的调用者,从而交出了CPU使用权,而当调用者再次调用next()或者send()的时候,又会返回到yield中断的地方,如果send有参数,还会将参数返回给yield赋值的变量,如果没有就和next()一样赋值为None。每次 next() 被调用时,生成器回复它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值),另一个关键的功能在于两次执行之间,局部变量和执行状态都自动的保存下来。这使函数很容易写,而且 比使用 self.index 和 self.data 之类的方式更清晰。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。综上所述,这个功能使得编写一个正规函数成为创建迭代器的最简单方法。
注意,第一次必须send(None)来开启协程
示例:
def bottom():
i = 0
while True:
i = yield i
print(i)
if i > 3:
raise StopIteration
def main():
b = bottom()
b.send(None)
i = 0
while 1:
try:
i = b.send(i + 1)
except StopIteration:
break
if __name__ == '__main__':
main()
三、
yield form
为了让生成器(带yield函数),能简易的在其他函数中直接调用,在Python3.3中引入了yield from。
示例:
def bottom():
i = 0
while True:
i = yield i
print(i)
if i > 3:
raise StopIteration
def top():
j = yield from bottom()
print(j)
def main():
b = top()
b.send(None)
i = 0
while 1:
try:
i = b.send(i + 1)
except StopIteration:
break
if __name__ == '__main__':
main()
在上述代码增加了top函数,此时使用了yield form后,top成为了一个新的生成器。
四、
async/await
在Python3.5中,引入了aync&await 语法结构,通过"aync def"可以定义一个协程代码片段,作用类似于Python3.4中的@asyncio.coroutine修饰符,而await则相当于"yield from"。
示例:
import asyncio
import aiohttp
async def bottom(i):
print(i)
async def main():
await asyncio.wait([
bottom(1),
bottom(2),
bottom(3)
])
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
要注意的是,并不是使用了async,await就实现了异步,这里还需要使用必须使用支持异步操作的非阻塞代码才能实现真正的异步,例如非阻塞异步I/O的库aiohttp,主要用于网络传输,同于request,这个库在服务端能发挥的性能威力让人侧目。上面示例没有给出。
异步编程还有更加强大的库支撑,例如uvloop,后面会再记载,现在可去https://magic.io/blog/uvloop-blazing-fast-python-networking/