Python协程与异步编程简述

  • 前言
  • 一、异步与协程
  • 二、协程的实现方式
  • 1.yield
  • 2.greenlet
  • 3.gevent
  • 4.asyncio
  • 5.async + await 关键字
  • 总结

前言

Python作为一门脚本语言,经常用于IO密集型的场合,所以,对于异步编程就有所要求。在Python里,处理多任务有三种方式:1.多线程 2.多进程 3.协程。 多线程是轻量级的多任务方式,但是由于GIL(全局解释器锁)导致其在Cpython下性能不能发挥出来,而且虽然有GIL,多线程也是有着线程安全的问题。而多进程占用的系统资源较多。协程作为一种用户态上下文切换方式的优点就体现出来了,它是轻量级的,比多线程占用的资源更少,但是执行速度却一点不慢。

一、异步与协程

说起异步就不得不说同步,在计算机领域内,**同步指的是如果一个进程在执行一个请求时,如果该请求需要等待一段时间才能返回信息,则改进程则会一直等待下去;异步则是进程不需要一直等待下去,而是转而去执行一些其他的操作。**在生活中异步的概念就相当于,假如你有一个洗衣服的任务,你把衣服准备好,然后放进洗衣机里,洗衣机洗衣服需要30分钟,而这段时间如果你是同步的,则你就站洗衣机旁边等,等它洗完了,然后把衣服拿出来晾干。异步的操作就是,在洗衣机洗衣服的时候,你去洗个碗,拖个地,等到听见“蹬”的一声,衣服洗好了,你再去把它拿出来晾干。
从上面的例子你可以看出,如果我们想异步洗衣服的时候你可以去干其他的事情,这说明,对于这种多任务,多等待(IO操作)的任务,使用异步的方式可以榨干性能。而且,从上面的例子可以看出,在进行异步操作的时候,我们需要保存任务的状态,保证在我们返回查看的任务的时候能返回,即你洗衣服的时候得确认你今天是去自家洗衣机洗的衣服,而不是丢给洗衣店洗的。在Python中,协程就是通过异步操作来实现并发的多任务的方式

二、协程的实现方式

1.yield

yield方式本质上是一种创建生成器的方式。

import time

def task1():
    while True:
        print("----task1----")
        yield
        time.sleep(1)


def task2():
    while True:
        print("----task2----")
        yield
        time.sleep(1)


def main():
    t_start = time.time()
    for i in range(10000):
        next(task1())
        next(task2())
    t_end = time.time()
    print("时间:%s" % (t_end - t_start))


if __name__ == '__main__':
    main()

2.greenlet

对yield的功能进行了封装,但切换任务的时候需要手动切换,在后面被gevent发扬。此处不赘述,想要了解可以搜索。

3.gevent

对greenlet的功能进行了进一步的封装,在遇到IO操作的时候自动切换任务,缺点是操作必须用自己的模块,后续打了monkey补丁后解决了这个问题。

# gevent实现协程(需要自己下)
import gevent
from gevent import monkey
import time


def task1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(1)


if __name__ == '__main__':
    monkey.patch_all()

    gevent.joinall([
        gevent.spawn(task1, 5),
        gevent.spawn(task1, 5),
        gevent.spawn(task1, 5)
    ])

4.asyncio

asyncio是python3以后推荐的协程创建方式。

import asyncio

@asyncio.coroutine
def fun1():
	print(111)
	yield from asyncio.sleep(1)
	print(222)

@asyncio.coroutine
def fun1():
	print(333)
	yield from asyncio.sleep(1)
	print(444)

# 创建事件循环
loop = asyncio.get_event_loop()
# 把任务添加至事件循环中并执行
loop.run_until_complete(fun1())
# python3.5后把上面两句可以合并为 asyncio.run()

5.async + await 关键字

python3.5后更新的方式,比asyncio更加简洁

import asyncio
import time


async def test(n, i):
    for j in range(n):
        print("work%s is runing" % i)
    await asyncio.sleep(1)
    return i


roll_list = [i for i in range(5)]

task = [test(5, i+1) for i in roll_list]

# 创建事件循环,并把把task列表里的任务加到时间循环里执行(asycio.wait()是随机选取一个任务执行的)
asyncio.run(asyncio.wait(task))

在某些第三方模块不支持异步操作时,可以使用异步+线程池/进程池的方式

import asyncio
import concurrent.futures
import time


# 模拟不支持异步的操作
def fun(value):
    time.sleep(1)
    print(value)
    return True


# 异步 + 多线程池
async def main(value):
    loop = asyncio.get_running_loop()

    fut = loop.run_in_executor(None, fun, value)

    result = await fut


list1 = [i**2 for i in range(10)]

task = [main(i) for i in list1]

asyncio.run(asyncio.wait(task))

总结

简单介绍了下Python协程与异步编程