通过Thread + Queue或ThreadPoolExecutor使用异步等待吗?

时间:2019-02-23 18:52:55

标签: python multithreading asynchronous async-await

我从未使用过async-await语法,但是在等待将来的响应时,我确实经常需要发出HTTP / S请求并解析响应。为了完成此任务,我目前使用ThreadPoolExecutor类,该类无论如何都异步执行调用。有效地,我可以达到(我相信)使用更多行代码来使用async-await所获得的相同结果。

假设当前的实现异步运行,我想知道async-await的实现与我原来使用线程和队列来管理工作程序的实现有何不同?它还使用信号量来限制工作人员。

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

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

实现的基本流程如下:

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

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

1 个答案:

答案 0 :(得分:2)

  

假设当前的实现异步运行,我想知道async-await实现与我原来使用线程和队列管理工作程序的实现有何不同

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

关于asyncio与使用线程的等效代码相比的优势,有几个:

  • 所有内容均在单个线程中运行,无论活动连接的数量如何。您的程序可以扩展到大量并发任务,而不会因为线程数量不合理而淹没了操作系统,也不必等待线程池中的空闲插槽开始下载。

  • 正如您所指出的那样,单线程执行不太容易出现竞争状况,因为可以在发生任务切换的点上用await清楚地标记,并且它们之间的所有内容实际上都是原子的。这种优点在小型线程程序中不太明显,在小型线程程序中,执行程序只是以即发即发的方式将任务交给线程,但是随着逻辑变得越来越复杂,线程开始共享更多状态(例如,由于缓存或某些原因)同步逻辑),这变得更加明显。

  • 异步/等待使您可以轻松地为监视,日志记录和清除之类的操作创建其他独立任务。使用线程时,那些线程不适合执行程序模型,并且需要其他线程,并且总是带有暗示线程被滥用的设计气味。使用asyncio,每个任务都可以像在自己的线程中运行一样,并使用await等待某些事情发生(并控制其他事务)-例如一个基于计时器的监视任务将由一个等待asyncio.sleep()的循环组成,但逻辑可能会非常复杂。尽管代码看起来是顺序的,但是每个任务都是轻量级的,并且对操作系统的权重不比分配的小对象大。

  • async / await支持可靠的取消,线程从未执行过并且可能永远不会执行。这通常被忽略,但是在异步中,完全有可能cancel运行中的任务,这会导致它从await唤醒,但会终止它。取消使使用超时,任务组和其他模式变得简单明了,这些超时,任务组和其他模式在使用线程时是不可能的或繁琐的工作。

另一方面,异步/等待的缺点是所有代码都必须异步。除其他外,这意味着您不能使用诸如请求之类的库,而必须切换到aiohttp之类的支持异步的替代方案。