协程
一、协程
概念
- 
协程 又称微线程(纤程),是一种用户态的轻量级线程 
- 
子程序 在所有的语言中都是层级调用的,比如A中调用B,B在执行过程中调用C,C执行完返回,B执行完返回,最后是A执行完毕。这是通过栈实现的,一个函数就是一个执行的子程序,子程序的调用总是有一个入口、一次返回,调用的顺序是明确的 
- 
理解协程 普通理解:线程是系统级别的,它们是由操作系统调度。协程是程序级别,由程序员根据需求自己调度。我们把一个线程中的一个个函数称为子程序,那么一个子程序在执行的过程中可以中断去执行别的子程序,这就是协程。也就是说同一个线程下的一段代码1执行执行着就中断,然后去执行另一段代码2,当再次回来执行代码1时,接着从之前的中断的位置继续向下执行 
- 
优点 a、最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。 b、不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 
- 
缺点 a、无法利用多核CPU,协程的本质是单个线程,它不能同时将多个CPU的多个核心使用上,失去了标准线程使用多CPU的能力。 b、进行阻塞操作(操作IO)会阻塞整个程序 
二、同步与异步
1、同步与异步的概念
- 
前言 python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率 IO密集型就是磁盘的读取数据和输出数据非常大的时候就是属于IO密集型 
 由于IO操作的运行时间远远大于cpu、内存运行时间,所以任务的大部分时间都是在等待IO操作完成,IO的特点是cpu消耗小,所以,IO任务越多,cpu效率越高,当然不是越多越好,有一个极限值。
- 
同步 指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行 
- 
异步 是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果 
2、同步与异步代码
- 
同步 
- 
异步 说明:后面的课程中会使用到asyncio模块,现在的目的是使同学们理解异步思想 三、asyncio模块1、概述- 
asyncio模块 是python3.4版本引入的标准库,直接内置了对异步IO的操作 
- 
编程模式 是一个消息循环,我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO 
- 
说明 到目前为止实现协程的不仅仅只有asyncio,tornado和gevent都实现了类似功能 
- 
关键字的说明 关键字 说明 event_loop 消息循环,程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数 coroutine 协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用 task 任务,一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态 async/await python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口 
 
- 
2、asyncio基本使用
- 
定义一个协程 
- 
等同于 创建一个消息循环loop = asyncio.get_event_loop() #将协程对象加入到消息循环 
 loop.run_until_complete(coroutine)
- 
创建一个任务 
- 
阻塞和await async可以定义协程,使用await可以针对耗时操作进行挂起,就与生成器的yield一样,函数交出控制权。协程遇到await,消息循环会挂起该协程,执行别的协程,直到其他协程也会挂起或者执行完毕,在进行下一次执行 
- 
获取返回值 
3、多任务
- 
同步 同时请求"百度", "阿里", "腾讯", "新浪"四个网站,假设响应时长均为2秒 
- 
异步 同时请求"百度", "阿里", "腾讯", "新浪"四个网站,假设响应时长均为2秒 使用ensure_future创建多任务 - 
封装成异步函数 
 使用loop.create_task创建多任务 
- 
- 
封装成异步函数 
使用asyncio.create_task创建多任务
4、Task 概念及用法
- 
Task,是 python 中与事件循环进行交互的一种主要方式。 创建 Task,意思就是把协程封装成 Task 实例,并追踪协程的 运行 / 完成状态,用于未来获取协程的结果。 
- 
Task 核心作用: 在事件循环中添加多个并发任务; 具体来说,是通过 asyncio.create_task() 创建 Task,让协程对象加入事件循环中,等待被调度执行。 **注意:**Python 3.7 以后的版本支持 asyncio.create_task() ,在此之前的写法为 loop.create_task() ,开发过程中需要注意代码写 法对不同版本 python 的兼容性。 
- 
需要指出的是,协程封装为 Task 后不会立马启动,当某个代码 await 这个 Task 的时候才会被执行。 当多个 Task 被加入一个 task_list 的时候,添加 Task 的过程中 Task 不会执行,必须要用 await asyncio.wait()或await asyncio.gather()将 Task 对象加入事件循环中异步执行。
- 
一般在开发中,常用的写法是这样的: -- 先创建 task_list 空列表; 
 -- 然后用 asyncio.create_task() 创建 Task;-- 再把 Task 对象加入 task_list ; -- 最后使用 await asyncio.wait 或 await asyncio.gather 将 Task 对象加入事件循环中异步执行。 注意: 创建 Task 对象时,除了可以使用 asyncio.create_task() 之外,还可以用最低层级的 loop.create_task() 或 asyncio.ensure_future() ,他们都可以用来创建 Task 对象,其中关于 ensure_future 相关内容本文接下来会一起讲。 
- 
Task 简单用法 
- 
task用法实例 
- 
代码执行结果如下: 
5、协程嵌套与返回值
使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来

- 
asyncio.wait和asyncio.gather的异同 - 异同点综述
 相同:从功能上看, asyncio.wait 和 asyncio.gather 实现的效果是相同的,都是把所有 Task 任务结果收集起来。 不同: asyncio.wait 会返回两个值: done 和 pending , done 为已完成的协程 Task , pending 为超时未完成的协程 Task ,需通过 future.result 调用 Task 的 result ;而 asyncio.gather 返回的是所有已完成 Task 的 result ,不需要再进行调用或其他操作,就可以得到全部结果。 - asyncio.wait 用法:
 最常见的写法是: await asyncio.wait(task_list) 。代码执行结果如下: - asyncio.gather 用法:
 最常见的用法是: await asyncio.gather(*task_list),注意这里task_list前面有一个*。代码执行结果如下: 
四、aiohttp与aiofiles
1、安装与使用
2、简单实例使用
aiohttp的自我介绍中就包含了客户端和服务器端,所以我们分别来看下客户端和服务器端的简单实例代码。
客户端:
这个代码是不是很简单,一个函数用来发起请求,另外一个函数用来下载网页。
3、入门
简单示范
首先是学习客户端,也就是用来发送http请求的用法。首先看一段代码,会在代码中讲述需要注意的地方:
在网络请求中,一个请求就是一个会话,然后aiohttp使用的是ClientSession来管理会话,所以第一个重点,看一下ClientSession:
在源码中,这个类的注释是使用HTTP请求接口的第一个类。然后上面的代码就是实例化一个ClientSession类然后命名为session,然后用session去发送请求。这里有一个坑,那就是ClientSession.get()协程的必需参数只能是str类和yarl.URL的实例。
当然这只是get请求,其他的请求都是支持的:
4、在URL中传递参数
有时候在发起网络请求的时候需要附加一些参数到url中,这一点也是支持的。
同时如果需要指定一个键对应多个值的参数,那么MultiDict就在这个时候起作用了。你可以传递两个元祖列表来作为参数:
5、读取响应内容
我们可以读取到服务器的响应状态和响应内容,这也是使用请求的一个很重要的部分。通过status来获取响应状态码,text()来获取到响应内容,当然也可以之计指明编码格式为你想要的编码格式:
6、非文本内容格式
对于网络请求,有时候是去访问一张图片,这种返回值是二进制的也是可以读取到的:
7、请求的自定义
ClientResponse(客户端响应)对象含有request_info(请求信息),主要是url和headers信息。 raise_for_status结构体上的信息会被复制给ClientResponseError实例。
(1) 自定义Headers
有时候做请求的时候需要自定义headers,主要是为了让服务器认为我们是一个浏览器。然后就需要我们自己来定义一个headers:
(2) 如果出现ssl验证失败的处理
(3) 自定义cookie
发送你自己的cookies给服务器,你可以为ClientSession对象指定cookies参数:
(4) 使用代理
有时候在写爬虫的时候需要使用到代理,所以aiohttp也是支持使用代理的,我们可以在发起请求的时候使用代理,只需要使用关键字proxy来指明就好,但是有一个很难受的地方就是它只支持http代理,不支持HTTPS代理。使用起来大概是这样:
使用起来大概是这样,然后代理记得改成自己的。
8、aiofiles文件读写
8.1 概述
平常使用的file操作模式为同步,并且为线程阻塞。当程序I/O并发次数高的时候,CPU被阻塞,形成闲置。
线程开启文件读取异步模式
用线程(Thread)方式来解决。硬盘缓存可以被多个线程访问,因此通过不同线程访问文件可以部分解决。但此方案涉及线程开启关闭的开销,而且不同线程间数据交互比较麻烦。
使用已编写好的第三方插件-aiofiles,支持异步模式
使用aio插件来开启文件的非阻塞异步模式。
8.2 安装方法
这个插件的使用和python原生open 一致,而且可以支持异步迭代
8.3 实例
打开文件
迭代
9、并发控制
semaphore,控制并发
实例
需要注意,当前对于Windows系统可能会有报错,原因出在文件名称有特殊字符,可以将特殊字符进行替换后即可
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号