python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python多线程操作全局变量

Python多线程操作全局变量的最佳方案

作者:detayun

本文详细探讨了Python多线程操作全局变量的常见问题及解决方案,重点推荐使用队列传递结果和使用锁保护等程操作,旨在提升代码性能与安全性,需要的朋友可以参考下

先说结论

多线程操作全局变量,核心矛盾是线程安全。Python因为GIL的存在,看似"安全",实则在非原子操作上照样会出bug。解决方案按推荐优先级排序:优先用队列(Queue)传参 → 用锁(Lock)保护 → 用线程局部存储(threading.local)隔离

一、为什么全局变量在多线程中是个坑?

先看一个经典翻车现场:

import threading

count = 0  # 全局变量

def worker():
    global count
    for _ in range(100000):
        count += 1  # 看似一行,实际是三步:读 → 改 → 写

threads = [threading.Thread(target=worker) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(count)  # 期望 1000000,实际经常是 99xxxx 这种奇怪的数

问题出在哪?

count += 1 不是原子操作,它等价于:

temp = count      # 第1步:读
temp = temp + 1   # 第2步:改
count = temp      # 第3步:写

两个线程可能同时读到同一个旧值,各自+1后写回,结果只增加了1。这叫竞态条件(Race Condition)

很多人以为Python有GIL就不会有线程安全问题——GIL只保证同一时刻只有一个线程执行Python字节码,但不能保证"读-改-写"这三步不被打断

二、四种解决方案,逐个拆解

方案1:用threading.Lock加锁(最常用)

import threading

count = 0
lock = threading.Lock()

def worker():
    global count
    for _ in range(100000):
        with lock:          # 核心:把读-改-写包在一个锁里
            count += 1

优点:简单直接,逻辑清晰。
缺点:锁会让线程串行执行,并发变并串,性能下降。
适用场景:写操作频繁、对性能要求不极端的场景。

方案2:用queue.Queue传参(最推荐)

不让多个线程直接改同一个全局变量,而是把结果发到队列里,由一个线程统一汇总:

import threading
import queue

result_queue = queue.Queue()

def worker(n):
    # 每个线程只算自己的部分,不碰全局变量
    local_sum = sum(range(n))
    result_queue.put(local_sum)  # 扔进队列

threads = [threading.Thread(target=worker, args=(100000,)) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

# 主线程统一收结果
total = 0
while not result_queue.empty():
    total += result_queue.get()
print(total)

为什么推荐?

这是我最推荐的方案。能不共享就不共享,是并发编程的第一原则。

方案3:用threading.local做线程隔离

如果每个线程需要"自己的一份"全局变量,用 threading.local

import threading

thread_local = threading.local()

def worker():
    # 每个线程拿到的是自己独立的副本,互不干扰
    thread_local.count = 0
    for _ in range(100000):
        thread_local.count += 1
    print(f"线程 {threading.current_thread().name} 的结果: {thread_local.count}")

threads = [threading.Thread(target=worker) for _ in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

适用场景:每个线程需要独立维护状态(如连接对象、计数器),不需要汇总。

方案4:用concurrent.futures+as_completed(现代写法)

from concurrent.futures import ThreadPoolExecutor, as_completed

def compute(n):
    return sum(range(n))

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(compute, 100000) for _ in range(10)]
    total = sum(f.result() for f in as_completed(futures))

print(total)

优点:代码简洁,自动管理线程池,结果通过 Future 对象返回,天然隔离。
适用场景:Python 3.2+,追求代码整洁的场景。

三、常见陷阱清单

陷阱表现正确做法
以为 += 是原子操作计数结果不对用锁或队列
锁的范围太大性能暴跌只锁必要的几行代码
忘了 global 声明UnboundLocalError函数内修改全局变量必须加 global
多线程 + 可变对象(list/dict)append/pop 也不是原子的同样需要锁保护
以为 GIL = 线程安全放松警惕GIL不保逻辑正确性,只保字节码执行互斥

四、选型决策树

需要多线程共享一个变量?
├── 不需要共享 → 用 Queue 传参 ✅(首选)
├── 每个线程要独立副本 → 用 threading.local ✅
├── 必须共享且写多 → 用 Lock 保护 ✅
└── 只是偶尔读 → 直接读,不用锁(GIL够用)

五、一句话总结

多线程操作全局变量的本质问题是"共享可变状态"。最好的解决方式不是加锁,而是消灭 共享——用队列传递结果,让每个线程只管自己那一份。

如果你正在写多线程代码,回头检查一下:有没有办法把全局变量删掉,换成参数传递或队列?能删就删,这比任何锁都靠谱。

以上就是Python多线程操作全局变量的最佳实践的详细内容,更多关于Python多线程操作全局变量的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文