代码之家  ›  专栏  ›  技术社区  ›  user9342520

在从主线程运行的异步IO事件循环中运行无限循环

  •  4
  • user9342520  · 技术社区  · 7 年前

    我编写的代码似乎可以满足我的要求,但我不确定这是否是一个好主意,因为它混合了线程和事件循环,以在主线程外运行无限循环。这是一段最简单的代码片段,它捕获了我正在做的事情的想法:

    import asyncio
    import threading
    
    msg = ""
    
    async def infinite_loop():
        global msg
        while True:
            msg += "x"
            await asyncio.sleep(0.3)
    
    def worker():
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        asyncio.get_event_loop().run_until_complete(infinite_loop())
    
    t = threading.Thread(target=worker, daemon=True)
    t.start()
    

    主要思想是,我有一个无限循环,每0.3秒操作一个全局变量。我希望这个无限循环从主线程运行,这样我仍然可以访问主线程中的共享变量。这在jupyter中特别有用,因为如果我调用 run_until_complete 在主线程中,我无法再与jupyter交互。我希望主线程可以交互访问和修改 msg 。在我的示例中,使用异步似乎没有必要,但我使用的是一个包含异步代码的库来运行服务器,因此这是必要的。我不熟悉python中的异步和线程,但我记得在某些地方读到/听说,将线程与异步IO结合使用会带来麻烦。。。这是个坏主意吗?我的方法是否存在任何潜在的并发问题?

    1 回复  |  直到 7 年前
        1
  •  3
  •   user4815162342    7 年前

    我不熟悉python中的异步和线程,但我记得在某些地方读到/听说,将线程与异步IO结合使用会带来麻烦。。。

    对于初学者来说,不鼓励将asyncio和线程混合使用,因为这会导致不必要的复杂性,并且通常是由于缺乏对如何正确使用asyncio的理解。不熟悉asyncio的程序员通常习惯于使用线程,将其用于更适合协同路由的任务。

    但是,如果您有充分的理由生成一个运行asyncio事件循环的线程,那么一定要这样做——没有什么需要在主线程中运行asyncio事件循环的。只需小心与事件循环本身交互(调用方法,如 call_soon ,则, create_task ,则, stop ,等等)仅来自运行事件循环的线程,即来自异步协同路由和回调。要安全地与其他线程(例如,在您的情况下是主线程)的事件循环进行交互,请使用 loop.call_soon_threadsafe() asyncio.run_coroutine_threadsafe()

    请注意,设置全局变量等不算“交互”,因为asyncio不观察这些变量。当然,由您来处理线程间同步问题,例如使用锁保护对复杂可变结构的访问。

    这是个坏主意吗?

    如果不确定是否混合线程和异步IO,可以问自己两个问题:

    • 考虑到asyncio提供了并行运行的协同路由,我甚至需要线程吗 run_in_executor 等待阻止代码?
    • 如果我有提供并行性的线程,那么我真的需要异步IO吗?

    您的问题很好地回答了这两个问题—您需要线程以便主线程可以与jupyter交互,您需要asyncio,因为您依赖于使用它的库。

    我的方法是否存在任何潜在的并发问题?

    GIL确保在一个线程中设置全局变量并在另一个线程中读取它是没有数据争用的,因此您所显示的内容应该很好。

    如果添加显式同步,例如多线程队列或条件变量,则应记住同步代码不得阻止事件循环。换句话说,你不能只是等待,比如说 threading.Event 在异步协同路由中,因为这将阻止所有协同路由。相反,您可以等待 asyncio.Event ,并使用 loop.call_soon_threadsafe(event.set) 从另一个线程。