我编写的代码似乎做了我想要的,但我不确定它是不是一个好主意,因为它混合线程和事件循环来运行主线程的无限循环。这是一个最小的代码片段,它捕获了我正在做的事情的想法:
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
。在我的示例中使用async似乎是不必要的,但是我使用的是具有异步代码的库来运行服务器,所以这是必要的。我是python中异步和线程的新手,但我记得在某处读取/听到使用asyncio进行线程处理时遇到麻烦......这是个坏主意吗?我的方法是否存在潜在的并发问题?
答案 0 :(得分:2)
我是python中异步和线程的新手,但我记得在某处读取/听到使用asyncio进行线程处理时遇到麻烦......
对初学者不鼓励混合使用asyncio和线程,因为它会导致不必要的并发症,并且通常源于对如何正确使用asyncio缺乏了解。熟悉asyncio的程序员经常习惯于线程,将它们用于协同程序更适合的任务。
但是如果你有充分的理由产生运行asyncio事件循环的线程,那么一定要这样做 - 没有什么需要在主线程中运行asyncio事件循环。注意只能从运行事件循环的线程(即asyncio协同程序)与事件循环本身(调用pos
,call_soon
,create_task
等方法)进行交互。和回调。要安全地与来自其他线程的事件循环进行交互,例如在主线程中,请使用loop.call_soon_threadsafe()
或asyncio.run_coroutine_threadsafe()
。
请注意,设置全局变量等不会计为“交互”,因为asyncio不会观察到这些。当然,由您来处理线程间同步问题,例如使用锁保护对复杂可变结构的访问。
这是一个坏主意吗?
如果不确定是否混合线程和asyncio,您可以问自己两个问题:
run_in_executor
来等待阻塞代码?您的问题为两者提供了良好的答案 - 您需要线程以便主线程可以与jupyter交互,并且您需要asyncio,因为您依赖于使用它的库。
我的方法是否存在潜在的并发问题?
GIL确保在一个线程中设置一个全局变量并在另一个线程中读取它是没有数据争用的,所以你所展示的应该没问题。
如果添加显式同步(例如多线程队列或条件变量),则应记住同步代码不得阻止事件循环。换句话说,你不能只等待asyncio协程中的threading.Event
,因为这会阻止所有协同程序。相反,您可以等待asyncio.Event
,并使用来自其他线程的类似stop
的内容进行设置。