python-3.x 为什么loop.run_in_executor仍然阻止事件循环?

dvtswwa3  于 2023-10-21  发布在  Python
关注(0)|答案(1)|浏览(133)

使用以下代码作为示例。其主要思想是让一个线程运行同步代码,并向主线程提交一个异步任务(事件循环)。

import asyncio
import concurrent.futures
from time import sleep

loop = asyncio.get_event_loop()
async def a():
    print('a')
    # simulate IO-bound async code
    await asyncio.sleep(0.1)
    print('b')

def c():
    print('c')
    x = loop.create_task(a())
    # simulate CPU-bound code
    sleep(1)
    print('d')

executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
future = loop.run_in_executor(executor, c)
loop.run_forever()

我希望看到的输出

c
a
[wait 0.1 second]
b
[wait 0.9 second]
d

但我真的看到

c
[wait 1 second]
d
a
[wait 0.1 second]
b

根据我的理解,主线程/循环不应该被run_in_executor future阻塞,并且应该在提交后开始运行runnc函数。
已在达尔文上的Python 3.11.5上测试(main,2023年8月24日,15:09:45)[Clang 14.0.3(clang-1403.0.22.14.1)]
有谁能解释这种行为吗?有没有什么方法可以从同步代码触发同步代码?
谢谢你,谢谢

j5fpnvbx

j5fpnvbx1#

问题是create_task不是thread_safe:尽管没有损坏的数据发生,因为集合,dicts和其他由GIL内部使用的结构,循环并不期望新的事情,它只是在等待它唯一的线程安全任务时出现,这是run_in_executor调用。
使用Rollcio中提供的线程安全调用将在内部执行正确的操作。这段代码的工作方式和你预期的一样:

import asyncio
import concurrent.futures
from time import sleep, time

_ellapsed_counter = time()
def ellapsed():
    global _ellapsed_counter
    tmp = time() - _ellapsed_counter
    _ellapsed_counter = time()
    return f"[ellapsed {tmp:.02f}s]\n"

loop = asyncio.get_event_loop()
async def a():
    print(ellapsed(), 'a')
    # simulate IO-bound async code
    await asyncio.sleep(0.1)
    print(ellapsed(), 'b')

async def helper():
    loop.create_task(a())

def c():
    print(ellapsed(), 'c')
    x = asyncio.run_coroutine_threadsafe(helper(), loop)
    # simulate CPU-bound code
    sleep(1)
    print(ellapsed(), 'd')

executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
future = loop.run_in_executor(executor, c)
loop.run_forevever()

相关问题