【问题标题】:Does calling thread.join() blocks the event loop in an asynchronous context?调用 thread.join() 会阻塞异步上下文中的事件循环吗?
【发布时间】:2018-11-02 18:24:36
【问题描述】:

我正在使用 aiohttp 实现 Web API,使用 gunicorn 部署并启用 UVloop --worker-class aiohttp.GunicornUVLoopWebWorker。因此,我的代码始终在异步上下文中运行。我的想法是在处理请求时实现并行作业以获得更好的性能。

我没有使用asyncio,因为我想要Parallelism,而不是Concurrency

我知道 python 中的multiprocessingGIL problem。但加入流程也适用于我的问题。

这是一个例子:

from aiohttp.web import middleware

@middleware
async def context_init(request, handler):
    request.context = {}
    request.context['threads'] = []
    ret = await handler(request)
    for thread in request.context['threads']:
        thread.join()
    return ret

考虑到thread.join()process.join() 阻塞了当前线程,这将阻塞事件循环(据我所知)。如何异步加入?我想要的可以形象地表示为:await thread.join()await process.join()

更新:

感谢@user4815162342,我能够为我的项目编写正确的代码:

中间件:

from aiohttp.web import middleware
from util.process_session import ProcessSession

@middleware
async def context_init(request, handler):
    request.context = {}
    request.context['process_session'] = ProcessSession()
    request.context['processes'] = {}
    ret = await handler(request)
    await request.context['process_session'].wait_for_all()
    return ret

实用程序:

import asyncio
import concurrent.futures
from functools import partial

class ProcessSession():

    def __init__(self):
        self.loop = asyncio.get_running_loop()
        self.pool = concurrent.futures.ProcessPoolExecutor()
        self.futures = []

    async def wait_for_all(self):
        await asyncio.wait(self.futures)

    def add_process(self, f, *args, **kwargs):
        ret = self.loop.run_in_executor(self.pool, partial(f, *args, **kwargs))
        self.futures.append(ret)
        return ret

class ProcessBase():

    def __init__(self, process_session, f, *args, **kwargs):
        self.future = process_session.add_process(f, *args, **kwargs)

    async def wait(self):
        await asyncio.wait([self.future])
        return self.future.result()

【问题讨论】:

    标签: python multithreading python-asyncio


    【解决方案1】:

    回答您的问题:是的,它确实阻塞了事件循环。

    我发现ThreadPoolExecutor 在这种情况下效果很好。

    from util.process_session import ProcessSession
    from concurrent.futures.thread import ThreadPoolExecutor
    import asyncio
    
    from aiohttp.web import middleware
    
    @middleware
    async def context_init(request, handler):
        request.context = {}
        request.context['threads'] = []
        ret = await handler(request)
        with ThreadPoolExecutor(1) as executor:
               await asyncio.get_event_loop().run_in_executor(executor, 
               functools.partial(join_threads, data={
                 'threads': request.context['threads']
               }))
        return ret
    
    def join_threads(threads):
        for t in threads:
            t.join()
    

    【讨论】:

      【解决方案2】:

      我找到了使用multiprocesses 的解决方案。可以使用Pool 来完成。标准库提供了一些“异步”方法(它并不是真正的异步,它只是将进程的初始化与进程的输出分开):apply_async

      使用简单的async wrapper,我成功实现了我想要的:

      from multiprocessing import Pool
      from async_converter import sync_to_async
      import asyncio
      
      def f(x):
          i = 0
          while i < 10000000 * x:
             i = i + 1
          print("Finished: " + str(x))
          return i
      
      
      async def run():
          print("Started with run")
          with Pool(processes=4) as pool:         # start 4 worker processes
              result1 = pool.apply_async(f, (10,)) # evaluate "f(10)" asynchronously
              result2 = pool.apply_async(f, (2,))
              res1 = await sync_to_async(result1.get)()
              print(res1)
              res2 = await sync_to_async(result2.get)()
              print(res2)
      
      async def dummy(output):
          print(output)
      
      async def main():
          # Schedule three calls *concurrently*:
          await asyncio.gather(
              run(),
              dummy("Nice"),
              dummy("Async"),
              dummy("Loop"),
              dummy("Perfect"),
              dummy("Dummy1"),
              dummy("Dummy2"),
              dummy("Dummy3"),
              dummy("Dummy4"),
              dummy("Dummy5"),
              dummy("Dummy6"),
              dummy("Dummy7"),
              dummy("Dummy8"),
              dummy("Dummy9"),
              dummy("Dummy10"),
          )
      
      loop = asyncio.get_event_loop()
      loop.run_until_complete(main())
      loop.close()
      

      输出:

      Perfect
      Dummy6
      Nice
      Dummy1
      Dummy7
      Started with run
      Dummy2
      Dummy8
      Dummy3
      Dummy9
      Async
      Dummy4
      Dummy10
      Loop
      Dummy5
      Finished: 2
      Finished: 10
      100000000
      20000000
      

      与 asyncio 的并行性:)

      【讨论】:

      • Asyncio 为此定义了run_in_executor。你可以给它一个ProcessPoolExecutor 作为执行者,它实现了你正在寻找的并行性。您的解决方案有效,但使用了通用的 sync_to_async 装饰器,该装饰器在单独的线程中运行每个 result.get()
      • @user4815162342 感谢您的洞察!我绝对会使用它。比我的临时解决方案好得多
      猜你喜欢
      • 2019-01-12
      • 2016-02-09
      • 2019-01-06
      • 2020-10-25
      • 1970-01-01
      • 2016-04-21
      • 1970-01-01
      相关资源
      最近更新 更多