Python网络与并发编程 18 asyncio 协程

协程

协程(coroutine)并不是一个系统层面上真实存在的东西,而是由程序员进行创造。

你可以理解为协程是用户态的“线程”,因此协程也被称为“微线程”或者“纤程(Fiber)”。

协程能够做到在单线程下实现多线程的并发操作,这是非常厉害的一点。

既然协程和线程很像,那么它对比线程有什么优势呢?

  • 协程和线程一样能够做切换,但是其切换代价远远小于线程,极大的提升了运行效率
  • 协程中修改共享数据时不需要为数据加锁,因为协程本身就是一个单线程

协程有2大重要的概念,如下所示:

  1. 作为用户态线程,它必然存在于内核态线程中,也就是说协程本身是一个非常纯粹的单线程
  2. 协程最重要的意义就是切换

普通的代码运行总是顺序执行的,而如果我们有某种机制让它能够遇见I/O后进行自动切换执行就非常牛逼了。

例如下面这个例子,普通的运行打印结果是1、2、3、4,而我们如果加上遇见I/O自动切换执行的策略的话它的打印结果就会变成1、3、2、4:

import time


def task_01():
    print(1)
    time.sleep(1)  # I/O操作
    print(2)


def task_02():
    print(3)
    time.sleep(1)  # I/O操作
    print(4)


task_01()
task_02()

# 1
# 2
# 3
# 4

如果真的实现了上述的功能,那么使用单线程实现并发就变的不是那么遥不可及了。

生成器

我们可以利用生成器函数的yield关键字来实现代码的切换,如下所示:

import time


def task_01():
    print(1)
    time.sleep(1)  # I/O操作
    yield          # 手动切换
    print(2)


def task_02():
    print(3)
    time.sleep(1)  # I/O操作
    yield          # 手动切换
    print(4)


# 创建生成器对象
genObject_01 = task_01()
genObject_02 = task_02()

# 待执行任务列表
task_list = [genObject_01, genObject_02]

# 开启循环
while 1:

    # 可执行任务列表
    executable_list = task_list.copy()
    # 已执行任务列表
    completed_list = []

    for run_generator in executable_list:
        try:
            next(run_generator)
        except StopIteration as e:
            # 如果任务执行完毕,添加到已执行任务列表中
            completed_list.append(run_generator)

    for end_generator in completed_list:
        # 从待执行任务列表、已执行任务列表中删除已完成任务
        task_list.remove(end_generator)
        executable_list.remove(end_generator)

    else:
        # 清空已完成任务列表
        completed_list.clear()

    if not task_list:
        break

# 1
# 3
# 2
# 4

就这样一个基础的协程就做好了,但是你可以发现它的编码难度较大。

并且每次遇见I/O操作后都需要我们手动进行切换,十分的不方便。

gevent模块

针对yield协程的缺点,我们可以利用gevent模块,让整个碰见I/O操作就切换的过程变为自动进行。

它是一个第三方模块,所以你需要手动进行安装下载:

$ pip3 install gevent

代码如下所示:

import gevent
import time

from gevent import monkey

# 声明:下面代码碰见I/O操作自动切换
monkey.patch_all()


def task_01():
    print(1)
    time.sleep(1)  # I/O操作,自动切换
    print(2)
    time.sleep(1)  # I/O操作,自动切换


def task_02():
    print(3)
    time.sleep(1)  # I/O操作,自动切换
    print(4)


# 创建协程对象
fiberObject_01 = gevent.spawn(task_01, )  # 后面可传递参数
fiberObject_02 = gevent.spawn(task_02, )

# 任务列表
task_list = [fiberObject_01, fiberObject_02]

# 开始执行
gevent.joinall(
    task_list
)

# 1
# 3
# 2
# 4

asyncio模块

Python3.4之后,官方提供了asyncio模块来提供对协程的支持。

下面是代码示例:

import asyncio


# 函数头部加上该装饰器,表明该函数是一个协程函数
@asyncio.coroutine
def task_01():
    print(1)
    yield from asyncio.sleep(1)  # I/O操作  自动切换
    print(2)


@asyncio.coroutine
def task_02():
    print(3)
    yield from asyncio.sleep(1)   # I/O操作  自动切换
    print(4)


# 创建协程对象,并将它包装为期程对象
fiberObject_01 = asyncio.ensure_future(task_01())
fiberObject_02 = asyncio.ensure_future(task_02())

# 任务列表
task_list = [fiberObject_01, fiberObject_02]

# 获取并开启循环
loop = asyncio.get_event_loop()

# 运行任务列表,并等待所有协程任务执行完毕
loop.run_until_complete(asyncio.wait(task_list))

# 1
# 3
# 2
# 4

我们要注意,如果一个协程函数中要调用另一个函数,则必须使用yield from关键字,yield form后面可以运行的对象类型:

  • 协程对象
  • 期程对象
  • task任务对象

另外,如果你想在协程函数中运行一些模块方法,那么一定要保证模块所提供的方法是异步方法,否则协程切换无效。

有关于yield from的使用,请参照Python基础生成器一章节。

async&awit语法

async和awit语法在Python3.5中被支持,它本质和asyncio模块使用没有任何区别,只是简化了语法。

  • async:用于定义协程函数,而不再使用@asyncio.coroutine装饰器来进行定义
  • <
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值