在Thread + Queue或ThreadPoolExecutor上使用async-await?

问题描述 投票:1回答:1

我从未使用过async-await语法,但我经常需要在等待将来的响应时发出HTTP / S请求并解析响应。为了完成这个任务,我目前使用ThreadPoolExecutor类来反向执行异步调用;实际上,我实现了(我相信)与使用async-await的更多代码行相同的结果。

在假设我当前的实现异步工作的情况下运行,我想知道async-await实现与我使用Threads和Queue来管理worker的原始实现有什么不同;它还使用信号量来限制工人。

该实施是在以下条件下设计的:

  • 可能有任意数量的请求
  • 活动请求总数可以是4
  • 仅在收到响应时发送下一个请求

实施的基本流程如下:

  1. 生成请求容器
  2. 创建一个ListeningQueue
  3. 对于每个请求,创建一个Thread并传递URL,ListeningQueue和Semaphore
  4. 每个Thread尝试获取信号量(限制为4个线程)
  5. 主线程继续在while检查ListeningQueue
  6. 当线程收到响应时,将其放入ListeningQueue并释放Semaphore
  7. 等待线程获取信号量(进程重复)
  8. 主线程处理响应,直到count等于请求数

因为我需要限制活动线程的数量,所以我使用信号量,如果我使用async-await尝试这个,我必须在主线程或async def中设计一些逻辑,以防止在限制时发送请求已达成。除了这个限制,我没有看到使用async-await更有用的地方。是通过消除线程来降低开销和竞争条件的机会吗?这是主要的好处吗?如果是这样,即使使用ThreadPoolExecutor进行异步调用,它也使用了一个Threads池,从而使async-await成为更好的选择?

python multithreading asynchronous async-await
1个回答
2
投票

在假设我当前的实现是异步工作的情况下运行,我想知道async-await实现将如何与我使用Threads和Queue来管理worker的原始实现不同

使用asyncio和async-await来实现非常相似的逻辑并不难,它有自己的semaphore版本,它的使用方式大致相同。有关使用固定数量的任务限制并行请求数或使用信号量的示例,请参阅this question的答案。

至于asyncio相对于使用线程的等效代码的优势,有几个:

  • 无论活动连接数是多少,一切都在一个线程中运行。您的程序可以扩展到大量并发任务,而不会使用不合理数量的线程淹没操作系统,或者下载必须等待线程池中的空闲插槽才能启动。
  • 正如您所指出的,单线程执行不太容易受到竞争条件的影响,因为任务切换可以发生的点清楚地用await标记,并且其间的所有内容都是有效的原子。这种优势在小型线程程序中不那么明显,其中执行程序只是以火灾和收集方式将任务交给线程,但随着逻辑变得越来越复杂并且线程开始共享更多状态(例如由于缓存或某些同步逻辑),这变得更加明显。
  • async / await允许您轻松地为监视,日志记录和清理等事务创建其他独立任务。当使用线程时,那些线程不适合执行器模型并且需要额外的线程,总是带有设计气味,表明线程被滥用。使用asyncio,每个任务可以就像它在自己的线程中运行一样,并使用await等待某些事情发生(并将控制权交给其他人) - 例如基于计时器的监视任务将包含一个等待asyncio.sleep()的循环,但逻辑可能是任意复杂的。尽管代码看起来是顺序的,但每个任务都是轻量级的,并且对操作系统的重要性不如小的分配对象。
  • async / await支持可靠的取消,线程从未做过,也可能永远不会。这经常被忽略,但是在asyncio中,cancel完全有可能成为一个正在运行的任务,这会导致它从await中醒来,但会终止它。取消可以直接实现超时,任务组和其他模式,这些模式在使用线程时是不可能的,也是一项繁重的工作。

另一方面,async / await的缺点是所有代码都必须是异步的。除此之外,它意味着你不能使用像请求这样的库,你必须切换到像aiohttp这样的asyncio感知的替代品。

© www.soinside.com 2019 - 2024. All rights reserved.