You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于concurrent.futures.ThreadPoolExecutor递归创建线程的执行逻辑疑问

关于concurrent.futures.ThreadPoolExecutor递归创建线程的执行逻辑疑问

咱们先把你的代码执行流程和背后的逻辑拆解开,你遇到的这些看起来“混乱”的输出,其实都是线程池调度规则+Python的GIL(全局解释器锁)共同作用的结果,我一步步给你捋清楚:

首先明确ThreadPoolExecutor的核心行为

ThreadPoolExecutor的submit()方法会把你的任务扔进线程池的任务队列,只要池里有空闲的worker线程(没超过max_workers上限),就会立刻启动这个任务——不是等当前正在执行的任务跑完,而是直接调度空闲线程去执行新任务。

另外,Python的线程是操作系统级的线程,但因为GIL的存在,同一时刻只有一个线程能执行Python字节码:

  • 当线程遇到阻塞操作(比如time.sleep()、IO请求),会主动释放GIL,让其他线程有机会执行;
  • 如果线程在执行非阻塞的代码(比如print、变量运算),会持有GIL直到执行完一定量的字节码,或者被操作系统强制调度切换。

分析你第一个代码的输出差异

没有time.sleep(30)的情况

你初始提交了f(5)到线程池,然后用cf.wait(tasks_list)等待初始任务完成:

  1. 线程池启动一个线程执行f(0):打印Task starting for 0,然后进入time.sleep(5)——这时候线程释放GIL,池里还有9个空闲worker。
  2. 但cf.wait(tasks_list)只盯着初始的那个future,当f(0)的sleep结束后,它会执行i +=1、提交f(5)(任务1)、打印Done sleeping for 0,这时候初始future完成,cf.wait返回,主线程立刻调用executor.shutdown(wait=True)。
  3. 虽然shutdown(wait=True)会等池里所有任务完成,但可能因为stdout的缓冲问题,或者你误以为程序停了就提前终止,导致输出只到Creating new task 2。

加上time.sleep(30)的情况

主线程在cf.wait之后进入time.sleep(30),主动释放GIL,让线程池里的所有任务都有充足的机会被调度执行:

  • 每个任务执行到time.sleep(5)时释放GIL,线程池会立刻启动新提交的任务(只要worker有空),所以你会看到“任务启动→sleep→提交新任务→新任务启动→前一个任务完成打印”这样交替的流程。

解释修改后代码的“异常”输出

你修改后的代码里,f函数的执行顺序是:

def f(x):
    global i
    print("Task starting for {}".format(i))
    print("***************")
    time.sleep(x)  # 这里会释放GIL
    if i < 10:
        i += 1
        print("Creating new task {}".format(i))
        executor.submit(f, x)  # 提交新任务,池里有空worker就立刻启动
    print("Done sleeping for {}".format(i-1))

你看到的这段输出:

Task starting for 2


Creating new task 3
Task starting for 3


Done sleeping for 2

原因是:

  1. f(2)执行完sleep(5)后,i变成3,打印Creating new task 3,然后submit(f,5)——线程池立刻启动这个新任务f(3)。
  2. 这时候f(2)还没执行最后的print("Done sleeping for 2"),操作系统的线程调度器把GIL抢走给了f(3),所以f(3)先打印了启动信息,然后进入sleep(5)释放GIL,这时候f(2)才拿回GIL,执行最后的打印。

这种“任务交替执行”的现象完全正常,线程的调度顺序由操作系统决定,加上GIL的释放时机,就会出现看起来“跳步”的输出。


几个关键结论帮你理清逻辑

  • submit()提交的任务不会等当前任务完成才启动,只要池里有空闲worker就会立刻执行;
  • 主线程的cf.wait()只会等待你传入的future对应的任务,不会自动等待后续递归提交的任务;
  • executor.shutdown(wait=True)会等待线程池里所有任务完成,不管这些任务是何时提交的;
  • 线程之间的执行顺序是不确定的,由操作系统调度+GIL释放时机共同决定,所以不要依赖任务的执行顺序来写代码。

备注:内容来源于stack exchange,提问作者schttrj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 09:38:11