多线程

Python多线程编程指南

1. 线程基础

Python通过threading模块提供多线程支持。每个进程至少有一个主线程(MainThread),可以创建额外的子线程来执行并发任务。

1.1 创建线程

import threading
import time

def worker():
    """线程执行的任务"""
    print(f'线程 {threading.current_thread().name} 正在运行...')
    time.sleep(1)
    print(f'线程 {threading.current_thread().name} 结束')

# 创建线程
t = threading.Thread(target=worker, name='WorkerThread')
t.start()  # 启动线程
t.join()   # 等待线程结束
print(f'主线程 {threading.current_thread().name} 结束')

1.2 线程属性

  • threading.current_thread(): 获取当前线程实例
  • threading.active_count(): 返回当前活跃线程数
  • threading.enumerate(): 返回当前所有线程列表

2. 线程同步与锁

多线程共享进程内存空间,当多个线程修改同一变量时可能产生竞争条件。

2.1 竞争条件示例

import threading

counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1

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

for t in threads:
    t.join()

print(f"最终计数器值: {counter}")  # 可能小于1000000

2.2 使用Lock解决竞争

import threading

counter = 0
lock = threading.Lock()

def safe_increment():
    global counter
    for _ in range(100000):
        with lock:  # 自动获取和释放锁
            counter += 1

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

for t in threads:
    t.join()

print(f"安全计数器值: {counter}")  # 保证为1000000

2.3 其他同步原语

  • threading.RLock: 可重入锁,同一线程可多次获取
  • threading.Condition: 条件变量,用于线程间通信
  • threading.Semaphore: 信号量,控制访问资源的线程数
  • threading.Event: 事件对象,用于线程间简单通知

3. GIL(全局解释器锁)问题

Python的GIL是CPython解释器的实现细节,它导致:

  1. 任何时候只有一个线程在执行Python字节码
  2. I/O密集型任务仍可从多线程受益
  3. CPU密集型任务无法有效利用多核

3.1 处理GIL限制的方案

  1. 使用多进程multiprocessing模块绕过GIL限制
  2. 使用C扩展:在C扩展中释放GIL执行计算
  3. 使用其他解释器:如Jython或IronPython没有GIL
  4. 异步编程asyncio适合I/O密集型任务

4. 线程池与最佳实践

4.1 使用ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor
import time

def task(n):
    print(f"处理任务 {n}")
    time.sleep(1)
    return n * n

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(task, i) for i in range(10)]
    results = [f.result() for f in futures]

print(f"所有任务结果: {results}")

4.2 多线程编程最佳实践

  1. 避免共享状态:尽量使用线程局部数据
  2. 使用队列通信queue.Queue是线程安全的
  3. 适当使用锁:锁粒度要尽可能小
  4. 考虑线程安全:优先使用线程安全的数据结构
  5. 处理异常:线程中的异常不会传播到主线程

5. 线程局部数据

import threading

# 创建线程局部存储
local_data = threading.local()

def show_data():
    print(f"{threading.current_thread().name}: {local_data.value}")

def worker(value):
    local_data.value = value
    show_data()

threads = [
    threading.Thread(target=worker, args=(i,), name=f"Thread-{i}")
    for i in range(3)
]

for t in threads:
    t.start()
for t in threads:
    t.join()

6. 多线程与多进程选择指南

特性多线程多进程
内存共享共享不共享
创建开销
通信成本
GIL影响受限制不受限
适用场景I/O密集型CPU密集型

7. 现代Python并发编程建议

  1. I/O密集型:考虑asyncio或线程池
  2. CPU密集型:使用multiprocessingconcurrent.futures.ProcessPoolExecutor
  3. 混合型:结合多进程和多线程
  4. 复杂场景:考虑celery等分布式任务队列

Python 3.10+提供了更强大的并发工具,如asyncio改进和新的concurrent.futures特性,开发者应根据具体需求选择合适的并发模型。