什么是线程?

想象你在一家餐厅工作:

💡 进程就像是整个餐厅 – 包含厨房、用餐区、收银台等

💡 线程就像是餐厅里的服务员 – 每个服务员可以同时处理多个任务(点餐、上菜、结账)

在计算机中:

  • 一个进程是程序执行的一个实例,拥有独立的内存空间
  • 线程是进程内的执行单元,所有线程共享相同的内存空间
  • 线程比进程更轻量级,创建和切换开销更小
  • Python的threading模块提供了操作线程的接口

多个线程在同一个进程中”同时”执行

为什么使用线程?

线程主要用于以下场景:

1. 提高程序响应性

在GUI程序中,用一个线程处理用户界面,另一个线程执行耗时操作,这样界面不会”卡死”

2. 充分利用I/O等待时间

当程序需要等待网络响应或文件读写时,CPU可以切换到其他线程执行任务

3. 多核CPU并行计算

虽然Python有GIL限制,但在I/O密集型任务中仍然能利用多核优势

但要注意:

  • 线程不适合CPU密集型任务(因为GIL限制)
  • 线程间共享数据可能导致竞争条件
  • 线程调试比单线程复杂

创建线程的两种方式

方法1:直接创建Thread对象

import threading import time # 定义线程要执行的任务 def task(name): print(f”线程 {name} 开始工作”) time.sleep(2) # 模拟耗时操作 print(f”线程 {name} 工作完成”) # 创建线程对象 thread1 = threading.Thread(target=task, args=(“A”,)) thread2 = threading.Thread(target=task, args=(“B”,)) # 启动线程 thread1.start() thread2.start() # 等待线程结束 thread1.join() thread2.join() print(“所有线程执行完毕”)

方法2:继承Thread类

class MyThread(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): print(f”线程 {self.name} 开始工作”) time.sleep(2) print(f”线程 {self.name} 工作完成”) # 使用自定义线程类 thread1 = MyThread(“A”) thread2 = MyThread(“B”) thread1.start() thread2.start() thread1.join() thread2.join()

线程的生命周期

线程生命周期

(图示:线程从创建到结束的状态变化)

状态 说明 转换方法
新建(New) 线程被创建但未启动 threading.Thread()
就绪(Ready) 线程可以运行,等待CPU时间片 start()方法调用后
运行(Running) 线程正在执行代码 获得CPU时间片
阻塞(Blocked) 线程等待I/O操作或同步锁 sleep(), wait(), acquire()等
终止(Dead) 线程执行完毕或异常退出 run()方法结束

线程同步机制

当多个线程访问共享资源时,可能出现竞争条件,导致数据不一致。

📖 类比:多个厨师同时使用同一个食谱

如果不加控制,厨师A可能正在添加食材时,厨师B修改了食谱,导致混乱

Python提供的同步工具:

  • Lock:互斥锁,确保一次只有一个线程访问资源
  • RLock:可重入锁,同一个线程可以多次获取
  • Semaphore:信号量,控制同时访问资源的线程数量
  • Event:事件,线程间通信机制
  • Condition:条件变量,用于复杂的线程同步

线程锁(Lock)

锁是最基本的同步机制,用于保护共享资源

import threading # 共享资源 counter = 0 lock = threading.Lock() def increment(): global counter for _ in range(100000): # 获取锁 lock.acquire() try: counter += 1 finally: # 释放锁 lock.release() # 创建多个线程 threads = [] for i in range(5): t = threading.Thread(target=increment) threads.append(t) t.start() # 等待所有线程完成 for t in threads: t.join() print(f”最终计数器值: {counter}”) # 应该是500000

⚠️ 注意:使用锁时要注意避免死锁,即两个线程互相等待对方释放锁

使用with语句可以简化锁的使用:

def increment_safe(): global counter for _ in range(100000): with lock: counter += 1

线程队列(Queue)

队列是线程间安全通信的最佳方式

📦 类比:传送带系统

生产者把产品放在传送带(队列)上,消费者从传送带上取产品

import threading import queue import time # 创建线程安全的队列 q = queue.Queue(maxsize=5) def producer(): for i in range(10): item = f”产品-{i}” q.put(item) print(f”生产者生产: {item}”) time.sleep(0.5) def consumer(): while True: item = q.get() if item is None: # 收到终止信号 break print(f”消费者消费: {item}”) time.sleep(1) q.task_done() # 创建生产者和消费者线程 prod_thread = threading.Thread(target=producer) cons_thread = threading.Thread(target=consumer) prod_thread.start() cons_thread.start() # 等待生产者完成 prod_thread.join() # 发送结束信号 q.put(None) cons_thread.join()

队列的常用方法:

  • put(item):添加元素
  • get():获取元素
  • task_done():通知任务完成
  • join():等待队列中所有任务完成

守护线程(Daemon Thread)

👼 类比:后台服务人员

当所有非守护线程(主线程)结束时,守护线程会自动退出

def background_task(): while True: print(“守护线程运行中…”) time.sleep(1) # 创建守护线程 daemon_thread = threading.Thread(target=background_task) daemon_thread.daemon = True # 设置为守护线程 daemon_thread.start() # 主线程工作 print(“主线程开始工作”) time.sleep(3) print(“主线程结束”) # 此时守护线程会自动终止

⚠️ 注意:

  • 守护线程会在主线程结束时立即终止,不会执行清理操作
  • 守护线程创建的子线程也是守护线程
  • 适合用于不重要的后台任务,如日志、监控等

线程局部数据(Thread-local Data)

每个线程拥有自己独立的数据副本

🎒 类比:每个学生有自己的书包

虽然都在同一个教室(进程)里,但每个学生(线程)的书包(数据)是独立的

import threading # 创建线程局部数据 local_data = threading.local() def show_data(): print(f”线程 {threading.current_thread().name} 的值: {local_data.value}”) def thread_task(value): # 每个线程设置自己的值 local_data.value = value show_data() # 创建多个线程 threads = [] for i in range(3): t = threading.Thread(target=thread_task, args=(i,)) threads.append(t) t.start() for t in threads: t.join()

输出结果:

线程 Thread-1 的值: 0 线程 Thread-2 的值: 1 线程 Thread-3 的值: 2

GIL全局解释器锁

Python多线程的最大限制

GIL(Global Interpreter Lock)是CPython解释器的特性:

  • 任何时候只有一个线程在执行Python字节码
  • 防止多线程同时访问Python对象导致状态不一致

对多线程编程的影响:

任务类型 GIL影响 建议方案
I/O密集型 影响小 多线程很有效
CPU密集型 影响大 使用多进程(multiprocessing)

虽然GIL存在,但在以下情况多线程仍有用:

  • 网络请求、文件I/O等操作会释放GIL
  • 使用C扩展可以绕过GIL
  • Python 3.2+改进了GIL实现,减少了对性能的影响

使用线程的注意事项

1. 避免过度使用线程

线程不是越多越好,过多的线程会增加上下文切换开销

2. 优先使用队列通信

队列比共享变量更安全,减少同步问题

3. 注意资源竞争

对共享资源的访问要加锁保护

4. 避免死锁

按固定顺序获取锁,使用超时机制

5. 区分CPU密集和I/O密集

CPU密集型任务考虑使用多进程

总结:何时使用多线程

✅ 处理I/O密集型任务(网络请求、文件读写)

✅ 需要保持用户界面响应

✅ 执行后台任务(日志、监控)

❌ 避免用于CPU密集型计算