【问题标题】:PIL and blocking calls with asyncioPIL 和使用 asyncio 阻塞调用
【发布时间】:2019-05-02 14:17:38
【问题描述】:

我有一个异步应用程序,它使用来自aiohttp 的服务器和带有asyncio.open_connection() 的异步套接字

我的代码包含来自 PIL 库的一些阻塞调用,例如

Image.save()
Image.resize()
  1. 即使调用没有阻塞太多时间,但如果我使用这些阻塞调用,我的 Web 服务器会冻结吗?更准确地说,事件循环是否有可能因为阻塞代码而错过事件?
  2. 如果是,这些与 asyncio 集成的功能的替代品是什么? PIL 没有异步版本。
  3. 一般来说,什么被认为是 asyncio 中的“阻塞代码”?除了套接字、读取文件等明显的操作。
    例如,os.path.join() 是否被认为可以?处理 numpy 数组怎么样?

【问题讨论】:

    标签: python numpy python-imaging-library python-asyncio blocking


    【解决方案1】:

    如果我使用这些阻塞调用,我的网络服务器会冻结吗?更多的 确切地说,事件循环是否有可能会错过事件,因为 阻塞代码?

    服务器将在执行图像功能时精确冻结。您不会错过任何事件,但所有事件处理都会延迟到图像函数执行的时间。

    冻结事件循环是一种糟糕的情况 - 你应该避免它。

    如果是,这些功能的替代品是什么,集成 与异步? PIL 没有异步版本。

    避免冻结事件循环的最简单和通用的方法 - 使用asyncio.run_in_executor 在另一个线程或另一个进程中执行阻塞函数。那里的代码 sn-p 显示了如何做到这一点,并包含对何时使用进程或线程的很好的解释:

    def blocking_io():
        # File operations (such as logging) can block the
        # event loop: run them in a thread pool.
        with open('/dev/urandom', 'rb') as f:
            return f.read(100)
    
    def cpu_bound():
        # CPU-bound operations will block the event loop:
        # in general it is preferable to run them in a
        # process pool.
        return sum(i * i for i in range(10 ** 7))
    

    我只想添加,对于每个 CPU 密集型操作,进程池可能并不总是一个好的解决方案。如果您的图像函数不需要太多时间(或者特别是如果您的服务器没有多个处理器内核),那么在线程中运行它们可能仍然更有效率。

    一般来说,什么被认为是 asyncio 中的“阻塞代码”?除了 像套接字,读取文件等明显的操作。例如,确实 os.path.join() 被认为可以吗?处理一个 numpy 数组怎么样?

    粗略地说任何函数都是阻塞的:它会阻塞事件循环一段时间。但是像os.path.join 这样的许多函数只需要很少的时间,所以它们不是问题,我们也不称它们为“阻塞”。

    当执行时间(和事件循环冻结)成为问题时,很难说出确切的限制,尤其是考虑到不同硬件的时间会有所不同。我有偏见的建议 - 如果您的代码在将控制权返回到事件循环之前需要(或可能需要)> 50 ms,请考虑将其阻塞并使用run_in_executor

    更新:

    谢谢,使用一个事件循环(主线程的)有意义吗? 并使用另一个线程将使用相同的循环添加任务?

    我不确定你在这里的意思,但我认为不是。我们需要另一个线程来运行一些作业,而不是在那里添加任务。

    我需要一些方法让线程在 图片处理完毕`

    等待run_in_executor 的结果或使用它开始任务。 run_in_executor - 是一个在后台线程中执行某些东西而不阻塞事件循环的协程。

    看起来像这样:

    thread_pool = ThreadPoolExecutor()
    
    
    def process_image(img):
        # all stuff to process image here
        img.save()
        img.resize()
    
    
    async def async_image_process(img):
        await loop.run_in_executor(
            thread_pool, 
            partial(process_image, img)
        )
    
    
    async def handler(request):
    
        asyncio.create_task(
            async_image_process(img)
        )
        # we use a task to return the response immediately,
        # read https://stackoverflow.com/a/37345564/1113207
    
        return web.Response(text="Image processed without blocking other requests")
    

    【讨论】:

    • 谢谢,使用一个事件循环(主线程的)并使用另一个线程使用相同的循环添加任务是否有意义?因为我需要一些方法让线程在图像处理完成后通知主线程。
    猜你喜欢
    • 2022-10-23
    • 1970-01-01
    • 2015-02-24
    • 2015-06-29
    • 2017-04-26
    • 1970-01-01
    • 2020-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多