首页 / Python3 入门教程 / 多线程编程

Python3 入门教程

多线程编程

本教程共 70 篇 · 第 56 篇 · 更新于 2026-07-22 · 约 4 分钟阅读

PythonPython3 入门教程多线程threadingLock锁ThreadLocal

56. 多线程编程

本节目标:学会用 threading 模块创建线程,理解锁的作用,掌握 ThreadLocal 的用法。

多进程好比开几家分店,各管各的账;多线程则是在同一家店里多请几个店员,共享同一套库存。线程更轻量,切换更快,但也带来了数据竞争的风险。

启动线程

Python 标准库提供两个线程模块:底层的 _thread 和高层的 threading。后者封装得更友好,平时写代码直接用 threading 就行。

import threading
import time

def worker(name, delay):
    for i in range(3):
        time.sleep(delay)
        print(f"[{name}] 执行第 {i+1} 次")

t1 = threading.Thread(target=worker, args=('线程A', 1))
t2 = threading.Thread(target=worker, args=('线程B', 1.5))
t1.start()
t2.start()
t1.join()
t2.join()
print("全部线程结束")

threading.current_thread() 可以拿到当前正在执行的线程对象。主线程的名字固定叫 MainThread,子线程默认叫 Thread-1Thread-2,你可以在创建时通过 name 参数自定义。

用类封装线程

除了传 target 函数,也可以继承 threading.Thread,重写 run 方法:

import threading

class MyThread(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)
        self.count = 0

    def run(self):
        for _ in range(3):
            self.count += 1
            print(f"{self.name}: {self.count}")

t = MyThread('自定义线程')
t.start()
t.join()

这种方式适合线程逻辑比较复杂、需要维护自身状态的场景。

锁:防止数据打架

多个线程同时读写同一个变量,结果可能出乎意料。看下面的例子:

import threading

balance = 0

def change(n):
    global balance
    for _ in range(100000):
        balance += n
        balance -= n

t1 = threading.Thread(target=change, args=(5,))
t2 = threading.Thread(target=change, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)  # 大概率不是 0

balance += n 看起来是一行代码,实际分解成「读、改、写」三步。两个线程穿插执行,就会互相覆盖,最后结果错乱。这就是典型的数据竞争

解决办法是给关键代码上锁:

lock = threading.Lock()

def change_safe(n):
    global balance
    for _ in range(100000):
        lock.acquire()
        try:
            balance += n
            balance -= n
        finally:
            lock.release()

acquire() 拿不到锁时会阻塞等待,release() 释放后其他线程才能继续。为了防漏释放,一定记得把业务代码包在 try...finally 里。

Tip

更 Pythonic 的写法是用上下文管理器:with lock:,离开作用域自动释放,省去手动 release() 的麻烦。

def change_safe(n):
    global balance
    for _ in range(100000):
        with lock:
            balance += n
            balance -= n

ThreadLocal:线程的私人储物柜

多线程共享全局变量很方便,但有时候每个线程需要自己的独立数据。比如 Web 服务器处理多个请求时,每个线程要保存当前用户的信息。

一种笨办法是把数据当成参数一层层往下传。函数调用链一长,参数列表会爆炸。另一种办法是用全局字典,以线程 ID 为 key 存取。这能行,但代码不好看。

threading.local() 提供了一种优雅的替代方案:

import threading

local_data = threading.local()

def process_user(name):
    local_data.user = name
    step1()
    step2()

def step1():
    print(f"步骤1,当前用户: {local_data.user}")

def step2():
    print(f"步骤2,当前用户: {local_data.user}")

t1 = threading.Thread(target=process_user, args=('Alice',))
t2 = threading.Thread(target=process_user, args=('Bob',))
t1.start()
t2.start()
t1.join()
t2.join()

local_data 是全局对象,但每个线程读写到的 user 属性都是自己的副本,互不干扰。你可以把它理解成一个「自动按线程分区的字典」。

Note

ThreadLocal 的变量在线程结束后会被回收,不用手动清理。但它只在当前线程内有效,不能跨线程传递数据。

小结

  • threading.Thread 启动线程简单直接,继承重写 run 适合复杂场景。
  • 共享变量必须加锁保护,Lock 是最基础的同步工具。
  • ThreadLocal 为每个线程提供独立存储空间,避免参数层层传递。
  • 多线程虽然方便,但受 GIL 限制,同一时刻只有一个线程在执行 Python 字节码。

来源:参考了 runoob「Python3 多线程/threading 模块」、liaoxuefeng「14.2. 多线程/14.3. ThreadLocal」等,改写后所得。