python中协程的理解_关于协程的一些理解

最近学习爬虫的时候,顺带学习了许多Python高级编程的东西,接触到了许多新的知识,越来越觉得有必要记录下来,毕竟自己的理解写下来印象要深刻许多,以后查阅的时候,自己的话理解起来要容易许多。

操作系统中讲到了许多多进程,多线程的知识。大致来讲,早期为了提高系统的并发性,用到多进程的时候,进程作为拥有资源和处理器调度的基本单位,每个进程之间拥有各自独立的地址空间,互不干涉,因此,进程切换的时候需要保存很多的信息,如上下文信息,资源信息等,切换开销比较大。于是后来有了多线程的概念,线程就作为处理机调度的基本单位,只拥有少量的必需的资源,如一些堆栈资源等,而进程就作为拥有资源的基本单位,一个进程可以拥有多个线程,进程内的多个线程共享进程拥有的资源且有共同的地址空间,也就是其共同的进程地址空间,每个线程负责处理一个任务,所以进行切换的时候,只是在线程在进程里面切换,且只需要保存少量的信息,大大节省了开销,进一步提高了并发程度。当然,不同进程中的线程切换的时候,就不只是线程切换了,一定会导致进程之间的切换。

一. 什么是协程

有了多进程(线程)就可以在同一时间内执行多个进程(线程)足以提高并发性能,协程和他们有什么不同呢?协程是一种用户态的轻量级线程,其调度完全由用户控制。写成拥有自己的上下文寄存器和栈。协程的存在,可以使一个协程对象由于某种原因阻塞的时候,不必一直等待当前协程运行完毕,而是把等待的时间利用起来去处理其他等待的协程,这和多进程多线程的思想是一样的。协程切换的时候,保存上下文信息,当其等待的条件满足时,恢复其上下文信息,而直接从上一次运行到的状态继续运行。

与线程相比,他有几个优点:

多个协程运行的时候,其实是在一个线程运行的,因此不需要考虑共享资源的加锁问题。

因为是在一个线程上运行,协程切换的时候,没有线程的切换开销,效率更高。

因为是在一个线程上运行,因此要实现多核CPU的并行,就需要采用多进程+协程的方式。

给人的感觉是:在多线程的每个线程上又开发了多协程,进一步提高并发性。

二 .

协程编程

在进行协程编程之前,首先需要理解几个概念

event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。

coroutine:中文翻译叫协程,在 Python

中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async

关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

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

另外我们还需要了解 async/await 关键字,它是从 Python 3.5 才出现的,专门用于定义协程。其中,async

定义一个协程,await 用来挂起阻塞方法的执行。

一)定义协程

简单来说,一个协程就是用async定义的一个函数,而调用该函数就会返回一个协程对象。要定义协程我们需要导入asyncio包。

sg_trans.gif

通过get_even_loop方法创建事件循环。另外我们发现,直接方法的时候并没有执行而是返回了一个协程对象。也就是说,通过调用async定义的方法来创造协程对象。之后通过事件循环的

run_until_complete 方法将协程对象注册到事件循环中并执行。因此该协程在11行的时候才执行输出 number

:1

注意:async定义的协程对象只有注册到循环事件中才能执行

Task是将协程进一步包装,使协程包含状态信息,可以用来了解协程对象的执行情况。

其实,运用事件循环的 run_until_complete 方法对协程对象注册时就自动把它封装成了一个

task,我们也可以显示的将协程对象包装成一个task再将其注册。对协程对象包装成task有两种方法:一个是用事件循环的

create_task 方法;一个是用 asyncio 的 ensure_future 方法

使用 create_task 方法

sg_trans.gif

使用 ensure_future 方法

sg_trans.gif

ensure_future 和 create_task 方法返回的都是 Task 对象 ,并且可以看到,Task 对象中多了运行状态的

pending 和 finished 信息。

二)绑定回调函数

sg_trans.gif

这里我们定义了一个 request

协程,里面没有打印请求的状态码。我们希望协程对象执行完毕以后就去执行回调函数,通过回调函数来完成打印。于是将协程对象包装成 Task

后,通过 task 的 add_done_callback(回调函数)就可以将 Task 绑定到回调函数上。task

执行完成后就会执行回调函数,这时 task 对象会作为实参传递给回调函数的形参 ,通过 Task 的 result 方法可以取得

Task 对象的 result 值。

注意:协程的 return 值将会作为Task的 result 值也就是将作为 Task 的结果,如果在

request 协程中没有返回值,那么 Task 的 result 将会为 None

三.

多任务协程

前面定义的协程只有一个请求,那么如果在事件循环中添加多个请求呢?如何在事件循环中添加多个协程对象,这时候就要用到

aysncic 的 wait() 方法,对多个协程对象打包,最后用事件循环的 run_until_complete

方法将打包的对象一起注册到事件循环中然后运行。

sg_trans.gif

当然也可以通过 [asyncio.ensure_future(request()) for i in range(5)]

将5个协程对象包装成 5个 Task 的 Tasks 列表,然后传到 wait 方法中将其打包,再注册到循环事件中并运行。

四.

异步协程编程

协程的作用就是在某个协程因为某种事件而阻塞的时候,不必等待,而是去处理其他的协程。因此,协程对于处理IO密集型的请求非常有效,下面具体看一下。

因为直接直接访问百度的请求是很快的,因此我们用 flask 框架写一个简单的 web 应用程序,并用 sleep

来降低服务响应的速度,也就是当某个协程对这个 web 应用程序请求服务的时候,就会遇到 sleep

而导致请求等待。因此,理论上来说,这时候事件循环中就不会等待这个协程的请求阻塞,而是去处理它里面的另一个协程的请求任务。

flask 框架应用程序

sg_trans.gif

这里在 run

函数中打开了多线程模式。因为如果不打开多线程,那么每个请求服务程序都会对他处理完了以后才会去处理下一个请求,因此就会在服务端出现瓶颈。开启了多线程,每个请求都会建立一个线程去处理,从而能够实现并发服务。我们在浏览器中输入本机的

9090 端口就可以看到打印输出的 Hello World 。

下面我们就用多个请求协程去请求这个 url:"localhost:9090 "。

sg_trans.gif

我们在服务器程序中让请求等待3s 才响应,加上一些网络因素,最后5个请求用时 20s

,这跟顺序执行5个请求好像没有什么区别,使用协程好像并不能提高效率。

要让协程异步执行,还要使用 await

关键字,让当前正在等待的操作挂起,转而去执行其他协程,直到其他的协程挂起或执行完毕(注意

await 后面要跟一个 coroutine 对象,否则会报错)。

除此之外,我们必须要使用支持异步操作的请求方式才可以实现真正的异步,requests库并没有实现异步请求,这里就需要用到

aiohttp 。aiohttp 是一个支持异步请求的库,利用它和 asyncio

配合我们可以非常方便地实现异步请求操作。

sg_trans.gif

在get协程中,对 get 请求和 text 请求我们都使用了 await

修饰,当他们请求阻塞的时候就会转而去服务其他协程请求。request 中的 5 个协程,对于 get 请求使用 await

挂起。可以看到,这次用时 4s 大大提高了效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值