答疑·限时优惠

如果,你想让我看见你的疑问并且百分之百的回答。可以加入我的知识星球。
AI悦创·进化岛
AI悦创·进化岛

内容提要:

  • 既然已经有了多线程了,那么协程的优势体现在哪?
  • 协程的并发是利用系统的 IO 复用实现,还是异步 IO 方式?"协程利用的事件循环",就是说事件循环也是一种异步方式。 既然协程本质上是单线程的,那这样的异步方式是如何实现的?
  • 协程在工作中都有哪些应用?
  • 多进程+协程会不会更快?
  • 如果我有三个独立任务 A、B、C,在每个任务中都会发不同个数的 HTTP 请求,那我应该在 A、B、C 中定义三个 Event Loop 还是定义一个复用?如果定义一个怎么复用?
  • 多进程+协程和线程池有什么区别吗?
  • 关于事件机制,是不是可以理解成也是异步方式的一种?
  • 在程序的什么地方用协程比较好,还是什么地方都可以用?
  • Scrapy 也是异步爬取,它和异步协程原理一样吗?关于二者的选用有何建议?
  • 如果在任务处理的过程中,又有新的新任务需要添加到事件循环中,这种是怎么处理的?
  • 写协程的时候需要注意的点是什么?
  • 协程操作数据库,会出现脏读、幻读吗?

问:既然已经有了多线程了,那么协程的优势体现在哪?

答:我们知道在 Python 中因为存在 GIL 的原因,导致在操作 CPU 密集型的操作时有限制。但是,对于 IO 密集型的没有这个影响。这个是公认的问题了,我们就看看其他的方面。

针对于多线程还存在以下几个问题:

  1. 线程属于内核级别的,由操作系统控制调度,其中在上下文切换时会消耗系统资源。
  2. 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题,这个就需要用到同步锁。
  3. 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多。

总的来说就是消耗资源大,对于协程,协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级,同时修改共享数据的时候就不需加锁。


问:协程的并发是利用系统的 IO 复用实现,还是异步 IO 方式?"协程利用的事件循环",就是说事件循环也是一种异步方式。 既然协程本质上是单线程的,那这样的异步方式是如何实现的?

答:协程的并发是异步 IO 方式实现的,其实在 Python 3.4 的时候使用 Yeild 的方式实现的,只是后来加入了语法糖 async/awati,协程遇到 IO 操作就切换,协程之所以能处理大并发,就是由于挤掉了 IO 操作,使得 CPU 一直运行。

事件循环和异步是两个概念,事件循环是执行我们的异步代码并决定如何在异步函数之间切换的对象。如果某个协程在等待某些资源,我们需要暂停它的执行,在事件循环中注册这个事件,以便当事件发生的时候,能再次唤醒该协程的执行。

运行异步函数我们首先需要创建一个协程,然后创建 Future 或 Task 对象,将它们添加到事件循环中,也就是说异步函数是需要靠事件循环运行的。协程本质上是单线程的,那这样的异步方式,是通过异步函数之间切换的实现的,因为没有阻塞,所以速度很多,看起来像是并发。实际上并不是并发。

一般是程序使用 await 进行切换。也就是挂起协程,类似 Yeild,所以有时候需要设置超时的,比如网络请求的 timeout。用了协程就意味着处处要用非阻塞的代码,比如说 Requests 是阻塞的。asyncio 内部之支持 Socket,不支持 HTTP 的异步,对于 Requests 来说又是阻塞的怎么办?这时候就出现了一个第三方的库 aiohttp,对这个库的使用以及其他的方面知识,后期再细讲 ,我先写个简单 Demo。大家自己现在需要准 Python 3.6,然后下载库 pip install aiohttp。

import asyncio #导入协程包
import aiohttp #导入aiohttp
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"}
async def getsource(url):
       conn=aiohttp.TCPConnector(verify_ssl=False)#防止ssl报错
       async with aiohttp.ClientSession(connector=conn) as session: #创建session
             async with session.get(url,headers=headers,timeout=60) as req: #获得请求
                    print(req.status) #获取网页标题
if __name__=="__main__":
         full_urllist=["https://www.baidu.com","https://www.cnblogs.com","https://www.jianshu.com"]
         event_loop = asyncio.get_event_loop() #创建事件循环
         tasks = [getsource(url) for url in full_urllist]
         results = event_loop.run_until_complete(asyncio.wait(tasks))#等待任务结束

这里只是并发请求了 3 个网站,时间的问题只做个演示。

full_urllist=["https://www.baidu.com","https://www.cnblogs.com","https://www.jianshu.com"]

这个地方可以自己定义个数,比如 1000 个 url。


问:协程在工作中都有哪些应用?

答:协程目前据我所知,有爬虫和 Web 开发相关应用,当然也可以用于协议的通信替代 Socket。这些都是后面会慢慢深入的,爬虫是我用的最多的所以我侧重于爬虫方面的开发。


问:多进程+协程会不会更快?

答:多进程+协程确实会更快,但对于 CPU 密集型计算来说多进程+协程确实有很大的优势。但是对于 IO 密集型操作单独的异步就够了,因为还涉及到其他的问题,比如你的网络环境或者网站的并发数限制。


问:如果我有三个独立任务 A、B、C,在每个任务中都会发不同个数的 HTTP 请求,那我应该在 A、B、C 中定义三个 Event Loop 还是定义一个复用?如果定义一个怎么复用?

答:在所有的应用中一般都是定义一个事件循环就可以,对于这个问题不同的并发数可以单独在各在的协程中进行,封装处理,然后使用 gather 进行并发就可以,gather 的就是组的形式并发。


问:多进程+协程和线程池有什么区别吗?

答:上面说了多进程+协程和协程了,途径不一样对于线程池和协程比较的话目前我还没有去确认,比起协程、线程池的话还是比较占资源的,这一点比多线程有优势。线程池这块更多的知识就没去了解了,更多用的是协程。

我在网上搜了下,线程池有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

问:关于事件机制,是不是可以理解成也是异步方式的一种?

答:事件机制就是 IO 多路复用,就是我们说的 select、poll、epoll,好处就在于单个 process 就可以同时处理多个网络连接的 IO,主要是事件轮询。异步是发送任务不用等到结果完成就返回做其他事情了。


问:在程序的什么地方用协程比较好,还是什么地方都可以用?

答:协程适用场景 IO 密集型操作的,比如网络爬虫,读写数据库等。对于 CPU 密集型操作体现不出优势,比如大型的科学计算,这个情况下就要结合多进程了。


问:Scrapy 也是异步爬取,它和异步协程原理一样吗?关于二者的选用有何建议?

答: Scrapy 是基于 twisted 的实现起来比较复杂,基于的协程 aiohttp 使用起来比较方便,因为协程还可以替代原有的事件循环,比如 uvloop,所以协程的使用更占优势。


问:如果在任务处理的过程中,又有新的新任务需要添加到事件循环中,这种是怎么处理的

答:这个时候就可以使用队列了,队列的话又有多种选择,一种是使用 asyncio 同步原语中的 Queue,再一个就是存储到 Redis 或者 Mongo。


问:写协程的时候需要注意的点是什么?

答:写协程注意点就是,你要时刻使用非阻塞的包,比如上面提到的 aiohttp 就是非阻塞的网络请求,然后我提到的 Mongo 和 Redis 都要使用异步版本,比如 aioredis、motor,目前异步社区已经比较成熟了,官方也提供了 aio 系列的各种异步库。aio-libs 就是:

大家可以在 Github 搜到。


问:协程操作数据库,会出现脏读、幻读吗?

答:我使用最多的是 Mongo 的异步版本 Motor,目前没有发现这个问题,脏数据更多的是爬虫解析过程中的问题。幻读的问题有待考察。

AI悦创·创造不同!
AI悦创 » 解析如何 10 分钟入门 Python 协程

Leave a Reply