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

Python3线程按创建顺序调度的原理?Py2与Py3代码差异解析

为什么Python 3.6中多线程向列表添加元素会保持顺序,而Python 2.7不会?

问题描述

我在Mac上运行以下代码时发现了一个奇怪的现象:在Python 2.7中输出False,但在Python 3.6中输出True。

from threading import Thread
def make_huge_list(amount):
    my_list = []
    def add_num(num):
        my_list.append(num)
    threads = [Thread(target=add_num, args=(i,)) for i in range(amount)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return my_list
if __name__ == '__main__':
    # 检查输出是否有序
    print(make_huge_list(100000) == list(range(100000)))

我知道Python 3为了优化调度公平性改进了GIL,但困惑为什么这段代码在Python 3.6中会输出True,同时想知道Python 3为何以及如何按线程创建顺序调度它们。


解答

咱们先澄清一个关键的点:Python 3并没有保证线程会严格按创建顺序执行,你看到的True只是这个特定轻量任务场景下的大概率结果,不是语言层面的承诺。不过结合GIL的改进,我们可以解释为什么这个场景下会出现这样的现象:

  1. Python 2 vs Python 3的GIL调度差异

    • Python 2的GIL采用的是基于字节码计数的释放策略:每执行100个字节码(可通过sys.setcheckinterval调整),解释器就会释放GIL,让其他线程有机会执行。这种策略很容易在密集计算场景下导致线程调度不公平,也可能在你启动线程的循环过程中就触发GIL切换,导致某个后续线程抢在前面的线程完成任务前执行,打乱append的顺序。
    • Python 3.2之后,GIL改成了基于时间片的调度逻辑:每个线程默认可以连续执行5毫秒(可通过sys.setswitchinterval调整),时间片结束后才会释放GIL。同时,新的调度逻辑在处理线程启动、I/O操作等场景时做了优化,让线程调度更公平。
  2. 你的代码场景为什么在Python 3中会有序
    你的每个线程任务只有一行my_list.append(num),对应的字节码非常少(大概只有5-6条),完全可以在一个时间片内执行完成。而你是按顺序启动线程:先启动处理0的线程,接着是1,直到99999。

    在Python 3的调度逻辑下,第一个线程启动后,几乎瞬间就完成了append操作并退出,此时调度器才会去调度下一个已经启动的线程。就这样,每个线程都在自己被调度到的第一个时间片内完成了任务,刚好按创建顺序执行了所有append,最终列表和range(100000)的顺序完全一致。

  3. 不要依赖这种行为
    如果你给add_num函数加一点耗时操作(比如time.sleep(0.0001)),哪怕只是微小的延迟,Python 3也会立刻出现顺序打乱的情况,输出False。多线程的执行顺序本质上是由操作系统和Python解释器的调度器共同决定的,GIL的改进只是让调度更公平,但绝不会保证执行顺序和线程创建顺序一致。


内容的提问来源于stack exchange,提问作者Yoav Glazner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:45:24