【Python进阶学习】—协程

系统 1832 0

前言

前面的文章提到过,python使用多线程,会因为GIL的原因导致多线程的使用效率低下,甚至比单个线程的处理速度还慢。然而在python编程中, 为了解决多线程之间上下文切换的开销,以及增加线程控制的灵活性,python引入了协程 。本文我们就来说一说python协程的特点和使用方法。

 

一、协程定义

定义:协程(Coroutine),又称微线程。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。

无论多线程和多进程,IO的调度更多取决于系统,而 协程的方式,调度来自用户 ,用户可以在函数中yield一个状态。使用协程可以实现高效的并发任务。

 

二、协程的实现

Python的在3.4中引入了协程的概念,可是这个还是以生成器对象为基础,3.5则确定了协程的语法。

实现协程通常可以引入一些python库来实现,其中最常用的就是asyncio,当然也有一些其他的如tornado和gevent都实现了类似的功能。这里我们就来了解一下asyncio

协程通过 async/await 语法进行声明 ,是编写异步应用的推荐方式。我们先来了解asyncio的几个概念名词:

            
              event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。

coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。

future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别

async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
            
          

为了解释上面的名词,我们一步步来实现简单的异步编程:

1、定义一个协程

            
              import time
import asyncio

# 定义一个协程并创建一个事件循环
loop = asyncio.get_event_loop()

# 协程对象coroutine需要在事件循环里面才能执行
loop.run_until_complete(coroutine)

            
          

2、协程对象

            
              async def sync_func(x):
    print('Waiting: ', x)

# 实例化一个协程对象
coroutine = sync_func(2)
            
          

由上面可以得出, 用async修饰的方法是一个协程对象,协程对象需要在协程的事件循环里面才能运行

3、创建一个task

            
              task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
            
          

事件循环除了可以运行协程对象,也可以运行任务。

在这里 task只是对协程对象coroutine进行了封装,task还可以绑定协程对象执行后的回调,甚至还可以封装成list数组等 ,逐个进行事件循环调用。

4、task绑定回调函数

            
              def callback(x):
    print(x)

task = asyncio.ensure_future(coroutine)
task.add_done_callback(functools.partial(callback, 2))
loop.run_until_complete(task)
            
          

其中callback是回调函数,执行完协程之后会执行回调函数。绑定回调也是用task封装协程对象的一个好处

5、协程的阻塞和await

            
              async def sync_func(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)


coroutine = sync_func(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
            
          

如上所述,如果协程对象函数里面带有 await ,是可以实现协程的运行阻塞的。上面例子执行结果是print之后sleep了2秒,这 期间可以让出控制器,loop可以去调用别的协程 ,然后再回来执行return,return的结果可以用task.result()来获取。

6、多个协程并发执行

            
              async def sync_func(x):
    print('Waiting: ', x)

    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

start = now()

coroutine1 = sync_func(1)
coroutine2 = sync_func(2)

tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
            
          

上述例子执行结果为:协程1print之后阻塞,再协程2print后也阻塞,最后协程1return,协程2再return。

7、协程停止:启动事件循环之后,马上ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。然后通过循环asyncio.Task取消future。

            
              try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    loop.stop()
    loop.run_forever()
finally:
    loop.close()
            
          

 

三、协程的使用场景

1、当一个程序变得很大而且复杂时,将其划分为子程序,每一部分实现特定的任务是个不错的方案。 子程序不能单独执行,只能在主程序的请求下执行,主程序负责协调使用各个子程序 。协程就是子程序的泛化。和子程序一样的事,协程只负责计算任务的一步;和子程序不一样的是,协程没有主程序来进行调度。

           【Python进阶学习】—协程_第1张图片

2、异步爬虫
  很多关心协程的朋友,大部分是用其写爬虫,这是因为协程能很好的解决IO阻塞问题。那么对于异步爬虫的需求,使用协程的方法大致如下:
(1)grequests;
(2)爬虫模块+gevent;
(3)aiohttp;
(4)scrapy框架+asyncio模块

3、协程池

类似于gevent,我们可以先创建好协程,放入一个协程池中,每次有任务请求的时候都由协程去执行,主线程进行统一管理和调度,这在一下I/O密集型的数据清洗等工作中可以提高很多效率。

 

四、协程的优缺点

1、执行效率高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显;
2、不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

3、缺点: 无法利用多核资源 ,协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上。

4、 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

参考链接:

https://thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/

https://www.jianshu.com/p/7690edfe9ba5

 


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论