Python列表不是线程安全的,因其append等操作非原子,多线程并发写入会导致数据错乱、IndexError或RuntimeError;必须用threading.Lock加锁保护所有读写操作,queue.Queue虽线程安全但不支持随机访问,不能替代list。

Python中没有原生线程安全的动态数组,list本身不是线程安全的
直接在多线程里读写同一个 list(比如用 append()、pop()、__setitem__)会导致数据错乱或 IndexError / RuntimeError,这不是偶发——只要存在并发写入,就可能出问题。Python 的 list 操作不是原子的,例如 my_list.append(x) 实际包含“检查容量→复制→写入→更新长度”多个步骤,线程切换可能卡在中间。
用 threading.Lock 包裹普通 list 是最直接可靠的方案
别被“高级结构”带偏,对绝大多数场景,自己加锁比找第三方包更可控、更易调试。
- 初始化时创建一个
threading.Lock()实例,和list一起封装进类里 - 所有会修改状态的方法(
append()、extend()、pop()、__setitem__()、__delitem__())必须先lock.acquire(),操作完再release();建议用with lock:确保释放 - 只读方法(如
__len__()、__getitem__())理论上可不加锁,但如果和写操作共存,且需要强一致性(比如要保证len()和后续__getitem__(0)不矛盾),也得加锁 - 避免在锁内做耗时操作(如网络请求、大文件读写),否则会严重拖慢其他线程
import threading
<p>class ThreadSafeList:
def <strong>init</strong>(self):
self._list = []
self._lock = threading.Lock()</p><pre class="brush:php;toolbar:false;">def append(self, item):
with self._lock:
self._list.append(item)
def pop(self, index=-1):
with self._lock:
return self._list.pop(index)
def __len__(self):
with self._lock: # 保证 len() 和后续访问的原子性
return len(self._list)
def __getitem__(self, index):
with self._lock:
return self._list[index]
为什么不用 queue.Queue 替代?
queue.Queue 是线程安全的,但它设计目标是生产者-消费者模型,不是通用动态数组:它不支持随机访问(q[5] 报错)、不能按索引删除、没有 extend()、也不提供 len() 的实时准确值(q.qsize() 在某些平台不精确)。如果你只需要 put()/get(),它很合适;但凡需要像数组一样索引、切片、批量修改,它反而会逼你绕路封装,增加复杂度和 bug 风险。
立即学习“Python免费学习笔记(深入)”;
性能与兼容性要注意这几点
加锁必然有开销,但对多数业务逻辑(非高频微秒级操作)影响极小。真正要注意的是死锁和锁粒度:
- 不要在持有锁时调用外部不可控函数(比如用户传入的回调),防止对方又去申请同一把锁
- 如果数组很大且读远多于写,可考虑
threading.RLock或读写锁(需第三方如readerwriterlock),但 Python GIL 下收益有限,通常不值得 - 该结构无法直接用于
multiprocessing(进程间内存不共享),跨进程要用multiprocessing.Manager().list(),但性能差很多 - 继承自
list不可行——你无法安全地重写所有魔术方法,且父类方法内部不走你的锁
线程安全的关键从来不是“用了什么结构”,而是“谁在什么时候修改了什么”。锁的位置比锁本身更重要。


















