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

关于Python中GIL与collections.deque线程安全性的疑问及示例需求

关于Python中GIL与collections.deque线程安全性的疑问及示例需求

嘿,这个问题问得特别戳痛点,很多刚接触Python多线程的同学都会被GIL和线程安全的关系绕晕,我来给你掰扯清楚~

先搞懂GIL和线程安全的核心矛盾

GIL确实保证同一时刻只有一个线程在执行Python字节码,但线程切换可能发生在任意字节码执行完毕之后。很多看似“单一操作”的Python代码,比如列表的append(),其实背后是由多个字节码指令组成的。如果线程在这些字节码之间切换,就可能导致数据结构处于不一致的状态。

举个简单的逻辑例子,列表的append()大致会做这几步:

  1. 检查列表容量是否足够
  2. 不够的话扩容分配新内存
  3. 将元素添加到新位置
  4. 更新列表的长度

这几步对应多个字节码,要是线程A刚做完步骤3,还没更新长度,GIL就切换给了线程B,线程B去读列表长度的时候拿到的还是旧值,后续操作自然就会出问题。

为什么collections.deque是线程安全的?

deque的关键操作(比如append()、appendleft()、popleft()、pop())都是原子操作——也就是说,这些操作在底层被实现成了不可分割的单一步骤,不会被GIL的线程切换打断。哪怕多个线程同时调用这些方法,也不会出现数据混乱,因为每个操作要么完全执行完,要么完全没开始。

具体示例对比

1. 列表的非线程安全演示

我们用多个线程同时往列表里添加元素,看看结果:

import threading

def add_to_list(lst, num):
    for _ in range(num):
        lst.append(1)

my_list = []
threads = []
# 开5个线程,每个线程加1000个元素
for _ in range(5):
    t = threading.Thread(target=add_to_list, args=(my_list, 1000))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"预期长度:5000,实际长度:{len(my_list)}")

运行这个代码,你会发现实际长度经常小于5000,偶尔甚至会出现索引错误——这就是因为append()不是原子操作,线程切换导致部分添加操作被“打断”了。

2. 用deque解决线程安全问题

把上面的列表换成deque,再试试:

import threading
from collections import deque

def add_to_deque(dq, num):
    for _ in range(num):
        dq.append(1)

my_deque = deque()
threads = []
for _ in range(5):
    t = threading.Thread(target=add_to_deque, args=(my_deque, 1000))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"预期长度:5000,实际长度:{len(my_deque)}")

不管你运行多少次,实际长度都会是5000,完全符合预期。这就是因为deque.append()是原子操作,不会被线程切换打断,保证了数据的一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 19:33:02