Python进程与线程管理:深入理解并发编程

181 阅读17分钟

引言

Python 提供了三种主要的并发模型,每种都有其适用场景:

  1. multiprocessing:通过创建独立进程绕过 GIL,实现真正的并行计算,最适合 CPU 密集型任务。
  2. threading:使用轻量级线程,共享内存空间,适合 I/O 密集型任务,但受 GIL 限制。
  3. asyncio:使用协程和事件循环实现单线程并发,极低的资源消耗,最适合高并发 I/O 密集型任务。

主要章节概览

一、并发编程基础概念

  • 进程与线程的本质区别
  • 并发与并行的概念
  • Python 三种并发模型对比

二、全局解释器锁(GIL)

  • GIL 的设计原因和工作机制
  • GIL 对性能的影响
  • Python 3.13 的 No-GIL 选项

三、多进程编程(multiprocessing)

  • Process 类的使用
  • 进程池(Pool)的高级应用
  • 进程间通信(Queue、Pipe、共享内存)

四、多线程编程(threading)

  • Thread 类的使用
  • 线程同步机制(Lock、RLock、Semaphore、Event、Condition)
  • 线程池(ThreadPoolExecutor)

五、异步编程(asyncio)

  • 协程和事件循环
  • async/await 语法
  • 异步上下文管理器和迭代器

六、并发模型选择指南

  • 任务类型分析(决策树)
  • 性能对比实验代码

七、常见陷阱与调试技巧

  • 竞态条件和死锁
  • 调试方法

八、性能优化技巧

一、并发编程基础概念

1.1 进程与线程的本质

在深入 Python 的并发编程之前,我们需要理解两个核心概念:进程(Process)和线程(Thread)。

进程是操作系统进行资源分配的最小单元。每个进程都有独立的内存空间、系统资源和执行环境。当你启动一个 Python 程序时,操作系统就会创建一个进程,为其分配内存、文件句柄等资源。进程之间相互独立,一个进程的崩溃不会影响其他进程。

线程是 CPU 调度的最小单元。一个进程至少包含一个线程(主线程),也可以创建多个线程。同一进程内的所有线程共享该进程的内存空间和资源,这使得线程间的通信相对简单,但也带来了数据竞争和同步的挑战。

1.2 并发与并行的区别

并发(Concurrency)是指多个任务在时间上重叠执行的能力。任务可能不是真正同时运行,而是通过快速切换给人一种同时执行的感觉。这种模式特别适合 I/O 密集型任务,因为任务大部分时间都在等待外部资源。

并行(Parallelism)则是指多个任务在同一时刻真正同时执行,这需要多核处理器的支持。每个任务运行在不同的 CPU 核心上,实现真正的同时计算。这种模式最适合 CPU 密集型任务。

image.png

1.3 Python 并发模型概览

Python 提供了三种主要的并发模型:

模型执行方式GIL影响适用场景资源消耗
threading多线程,单进程受限于GILI/O密集型任务
multiprocessing多进程完全独立CPU密集型任务
asyncio协程,单线程无影响高并发I/O任务极低

二、全局解释器锁(GIL)

2.1 GIL 的本质与设计原因

全局解释器锁(Global Interpreter Lock,简称 GIL)是 CPython 解释器的一个关键特性,也是 Python 并发编程中最具争议的话题之一。

GIL 是什么?

GIL 是一个互斥锁(mutex),它确保在任何时刻只有一个线程能够执行 Python 字节码。即使在多核处理器上运行多线程 Python 程序,同一时刻也只有一个线程在执行。

为什么需要 GIL?

GIL 的引入主要基于以下两个原因:

  1. 简化内存管理:Python 使用引用计数(reference counting)进行内存管理。每个对象都有一个引用计数器,记录有多少引用指向该对象。当引用计数降至零时,对象会被自动回收。这个计数器需要在多线程环境下保持线程安全。
  2. 避免死锁:如果为每个对象或数据结构单独加锁,就会存在多个锁,容易导致死锁。GIL 通过使用单一的全局锁简化了这个问题,避免了复杂的锁管理机制。

2.2 GIL 的工作机制

当 Python 线程执行时,它必须先获取 GIL。一旦获得 GIL,线程就可以执行 Python 字节码,直到以下情况之一发生:

  1. 线程主动释放 GIL(例如执行 I/O 操作)
  2. 线程执行了一定数量的字节码指令(默认约为 5ms 的执行时间)
  3. 线程被操作系统调度器强制挂起
import dis

def increment(n):
    n += 1
    return n

# 查看字节码指令
dis.dis(increment)

输出显示 n += 1 操作实际上由多个字节码指令组成:

LOAD_FAST (加载变量n)
LOAD_CONST (加载常量1)
INPLACE_ADD (执行加法)
STORE_FAST (存储结果)

这意味着看似原子的操作在 Python 层面并不是原子的,因此在多线程环境下仍然需要同步机制。

2.3 GIL 的性能影响

对 CPU 密集型任务的影响

在 CPU 密集型任务中,GIL 会严重限制多线程的性能。由于同一时刻只有一个线程能执行,多线程程序的性能可能还不如单线程程序,因为线程切换会带来额外的开销。

对 I/O 密集型任务的影响

在 I/O 密集型任务中,GIL 的影响相对较小。当线程执行 I/O 操作时(如网络请求、文件读写),它会主动释放 GIL,允许其他线程执行。因此,多线程在处理 I/O 密集型任务时仍然有效。

2.4 Python 3.13 的 No-GIL 选项

2024年10月,Python 3.13 引入了实验性的**自由线程(free-threaded)**构建选项,允许在编译时禁用 GIL。这个重大变化基于 PEP 703 提案,采用了以下技术:

  • 偏向引用计数(Biased Reference Counting):优化引用计数的更新机制
  • 延迟引用计数(Deferred Reference Counting):延迟某些引用计数的更新
  • 不朽对象(Immortal Objects):某些对象永不销毁,避免引用计数操作

然而,移除 GIL 是一个渐进的过程,预计需要数年时间才能成为默认选项。

三、多进程编程(multiprocessing)

3.1 multiprocessing 模块概述

multiprocessing 模块是 Python 标准库中用于并行处理的核心模块。它通过创建独立的进程来绕过 GIL 的限制,实现真正的并行计算。

核心特性

  • 每个进程拥有独立的 Python 解释器和内存空间
  • 完全避开 GIL 的限制
  • 适合 CPU 密集型任务
  • 提供类似 threading 模块的 API

3.2 进程创建方法

Python 提供了三种进程启动方法:

  1. spawn(Windows 默认):父进程启动全新的 Python 解释器进程,子进程只继承运行所需的必要资源
  2. fork(Unix 默认):父进程使用 os.fork() 创建子进程,子进程复制父进程的内存空间
  3. forkserver:启动服务器进程,后续进程创建由服务器进程 fork 生成
import multiprocessing

# 设置启动方法(需要在主程序开始处调用)
multiprocessing.set_start_method('spawn')

3.3 Process 类的使用

基本用法

from multiprocessing import Process
import os

def worker(name):
    print(f'Worker {name} (PID: {os.getpid()}) starting')
    # 执行一些计算密集型任务
    result = sum(i * i for i in range(10**7))
    print(f'Worker {name} finished with result: {result}')

if __name__ == '__main__':
    # 创建进程
    processes = []
    for i in range(4):
        p = Process(target=worker, args=(f'Process-{i}',))
        processes.append(p)
        p.start()  # 启动进程
    
    # 等待所有进程完成
    for p in processes:
        p.join()
    
    print('All processes completed')

继承 Process 类

from multiprocessing import Process
import time

class CustomWorker(Process):
    def __init__(self, task_id):
        super().__init__()
        self.task_id = task_id
    
    def run(self):
        """重写 run 方法定义进程行为"""
        print(f'Task {self.task_id} starting')
        time.sleep(2)
        print(f'Task {self.task_id} completed')

if __name__ == '__main__':
    workers = [CustomWorker(i) for i in range(3)]
    
    for w in workers:
        w.start()
    
    for w in workers:
        w.join()

3.4 进程池(Pool)

进程池提供了更高级的并行处理接口,特别适合批量处理大量任务。

Pool 的核心方法

from multiprocessing import Pool
import time

def compute_square(n):
    """计算平方"""
    time.sleep(0.1)  # 模拟计算
    return n * n

if __name__ == '__main__':
    # 创建进程池(默认使用 CPU 核心数)
    with Pool(processes=4) as pool:
        # map: 阻塞式批量处理
        numbers = range(20)
        results = pool.map(compute_square, numbers)
        print(f'map results: {results}')
        
        # map_async: 非阻塞式批量处理
        async_result = pool.map_async(compute_square, numbers)
        # 可以继续做其他事情
        print('Doing other work...')
        # 获取结果(会阻塞直到完成)
        results = async_result.get()
        print(f'map_async results: {results}')
        
        # apply: 处理单个任务(阻塞)
        result = pool.apply(compute_square, (10,))
        print(f'apply result: {result}')
        
        # apply_async: 处理单个任务(非阻塞)
        async_result = pool.apply_async(compute_square, (10,))
        result = async_result.get()
        print(f'apply_async result: {result}')

Pool vs Process 对比

特性PoolProcess
任务类型同质化批量任务异质化独立任务
进程管理自动管理进程池手动管理每个进程
资源复用进程可复用每次创建新进程
适用场景大量相似任务少量不同任务
易用性简单易用需要更多控制

3.5 进程间通信(IPC)

由于进程间内存隔离,需要专门的机制来实现数据交换。

Queue(队列)

from multiprocessing import Process, Queue

def producer(queue):
    """生产者进程"""
    for i in range(5):
        print(f'Producing item {i}')
        queue.put(i)
    queue.put(None)  # 发送结束信号

def consumer(queue):
    """消费者进程"""
    while True:
        item = queue.get()
        if item is None:
            break
        print(f'Consuming item {item}')

if __name__ == '__main__':
    q = Queue()
    
    p1 = Process(target=producer, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    
    p1.start()
    p2.start()
    
    p1.join()
    p2.join()

Pipe(管道)

from multiprocessing import Process, Pipe

def sender(conn):
    """发送端"""
    messages = ['Hello', 'World', 'from', 'Pipe']
    for msg in messages:
        conn.send(msg)
    conn.close()

def receiver(conn):
    """接收端"""
    while True:
        try:
            msg = conn.recv()
            print(f'Received: {msg}')
        except EOFError:
            break

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    
    p1 = Process(target=sender, args=(child_conn,))
    p2 = Process(target=receiver, args=(parent_conn,))
    
    p1.start()
    p2.start()
    
    p1.join()
    p2.join()

共享内存(Shared Memory)

from multiprocessing import Process, Value, Array

def increment_shared(shared_value, shared_array):
    """修改共享内存"""
    with shared_value.get_lock():  # 使用锁保证线程安全
        shared_value.value += 1
    
    for i in range(len(shared_array)):
        shared_array[i] = shared_array[i] * 2

if __name__ == '__main__':
    # 'd' 表示 double 类型,'i' 表示 int 类型
    shared_num = Value('d', 0.0)
    shared_arr = Array('i', range(10))
    
    processes = []
    for _ in range(5):
        p = Process(target=increment_shared, args=(shared_num, shared_arr))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print(f'Final value: {shared_num.value}')
    print(f'Final array: {list(shared_arr)}')

IPC 方法比较

方法特点性能适用场景
Queue线程/进程安全,FIFO中等多生产者-消费者模式
Pipe双向通信,简单快速两个进程间简单通信
Shared Memory直接内存访问最高大量数据共享
Manager支持复杂对象较低跨网络共享对象

四、多线程编程(threading)

4.1 threading 模块概述

threading 模块是 Python 标准库中用于创建和管理线程的核心模块。虽然受到 GIL 的限制,但对于 I/O 密集型任务仍然非常有效。

特点

  • 线程共享同一进程的内存空间
  • 创建和切换开销小于进程
  • 受 GIL 限制,无法实现真正的并行计算
  • 适合 I/O 密集型任务

4.2 Thread 类的使用

基本用法

import threading
import time

def worker(name, delay):
    """工作线程函数"""
    print(f'{name} starting')
    time.sleep(delay)
    print(f'{name} finished after {delay} seconds')

# 创建线程
threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(f'Thread-{i}', i+1))
    threads.append(t)
    t.start()

# 等待所有线程完成
for t in threads:
    t.join()

print('All threads completed')

继承 Thread 类

import threading
import time

class WorkerThread(threading.Thread):
    def __init__(self, name, task_queue):
        super().__init__()
        self.name = name
        self.task_queue = task_queue
        self.daemon = True  # 设置为守护线程
    
    def run(self):
        """重写 run 方法"""
        while True:
            task = self.task_queue.get()
            if task is None:
                break
            print(f'{self.name} processing task: {task}')
            time.sleep(1)
            self.task_queue.task_done()

# 使用示例
from queue import Queue

task_queue = Queue()
worker = WorkerThread('Worker-1', task_queue)
worker.start()

for i in range(5):
    task_queue.put(f'Task-{i}')

task_queue.join()  # 等待所有任务完成
task_queue.put(None)  # 发送停止信号
worker.join()

4.3 线程同步机制

由于线程共享内存,需要同步机制防止数据竞争。

Lock(互斥锁)

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        lock.acquire()  # 获取锁
        try:
            counter += 1
        finally:
            lock.release()  # 释放锁

# 使用 with 语句更简洁
def increment_with_context():
    global counter
    for _ in range(100000):
        with lock:  # 自动获取和释放锁
            counter += 1

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

print(f'Final counter: {counter}')

RLock(可重入锁)

import threading

class Counter:
    def __init__(self):
        self.value = 0
        self.lock = threading.RLock()  # 可重入锁
    
    def increment(self):
        with self.lock:
            self.value += 1
            self.double()  # 可以在持有锁时再次获取
    
    def double(self):
        with self.lock:  # 同一线程可以多次获取
            self.value *= 2

counter = Counter()
counter.increment()
print(counter.value)  # 输出: 2

Semaphore(信号量)

import threading
import time

# 限制同时访问资源的线程数
semaphore = threading.Semaphore(3)

def access_resource(name):
    print(f'{name} waiting for resource')
    with semaphore:
        print(f'{name} acquired resource')
        time.sleep(2)
        print(f'{name} releasing resource')

threads = [threading.Thread(target=access_resource, args=(f'Thread-{i}',)) 
           for i in range(10)]

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

Event(事件)

import threading
import time

event = threading.Event()

def waiter():
    print('Waiting for event...')
    event.wait()  # 阻塞直到事件被设置
    print('Event received!')

def setter():
    print('Preparing to set event...')
    time.sleep(3)
    event.set()  # 设置事件
    print('Event set!')

t1 = threading.Thread(target=waiter)
t2 = threading.Thread(target=setter)

t1.start()
t2.start()

t1.join()
t2.join()

Condition(条件变量)

import threading
import time

condition = threading.Condition()
items = []

def consumer():
    with condition:
        while len(items) == 0:
            print('Consumer waiting...')
            condition.wait()  # 等待通知
        item = items.pop()
        print(f'Consumer got: {item}')

def producer():
    time.sleep(1)
    with condition:
        items.append('item')
        print('Producer added item')
        condition.notify()  # 通知等待的线程

t1 = threading.Thread(target=consumer)
t2 = threading.Thread(target=producer)

t1.start()
t2.start()

t1.join()
t2.join()

4.4 线程池(ThreadPoolExecutor)

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def download_file(url):
    """模拟文件下载"""
    print(f'Downloading {url}')
    time.sleep(2)
    return f'Content from {url}'

urls = [f'http://example.com/file{i}' for i in range(5)]

# 使用线程池
with ThreadPoolExecutor(max_workers=3) as executor:
    # submit 方法:提交单个任务
    futures = [executor.submit(download_file, url) for url in urls]
    
    # 按完成顺序获取结果
    for future in as_completed(futures):
        result = future.result()
        print(f'Got result: {result}')

# map 方法:批量处理
with ThreadPoolExecutor(max_workers=3) as executor:
    results = executor.map(download_file, urls)
    for result in results:
        print(f'Got result: {result}')

五、异步编程(asyncio)

5.1 asyncio 模块概述

asyncio 是 Python 3.4 引入的异步 I/O 框架,通过协程和事件循环实现单线程并发。

核心概念

  • 协程(Coroutine):可以暂停和恢复的函数
  • 事件循环(Event Loop):调度和执行协程的核心机制
  • Future:表示异步操作的结果
  • Task:对协程的封装,由事件循环调度

特点

  • 单线程执行,避免线程切换开销
  • 适合高并发 I/O 密集型任务
  • 使用 async/await 语法
  • 不受 GIL 影响(因为是单线程)

5.2 协程的基本用法

定义和运行协程

import asyncio

async def hello():
    """协程函数使用 async def 定义"""
    print('Hello')
    await asyncio.sleep(1)  # 暂停执行,释放控制权
    print('World')
    return 'Done'

# Python 3.7+ 推荐用法
result = asyncio.run(hello())
print(result)

# 低级 API(通常不推荐)
loop = asyncio.get_event_loop()
result = loop.run_until_complete(hello())
loop.close()

并发执行多个协程

import asyncio
import time

async def fetch_data(id, delay):
    """模拟异步获取数据"""
    print(f'Fetching data {id}...')
    await asyncio.sleep(delay)
    return f'Data {id}'

async def main():
    # 方法 1: 使用 gather 并发执行
    results = await asyncio.gather(
        fetch_data(1, 1),
        fetch_data(2, 2),
        fetch_data(3, 1)
    )
    print(f'Results: {results}')
    
    # 方法 2: 使用 create_task
    task1 = asyncio.create_task(fetch_data(4, 1))
    task2 = asyncio.create_task(fetch_data(5, 2))
    
    result1 = await task1
    result2 = await task2
    print(f'Task results: {result1}, {result2}')

start = time.time()
asyncio.run(main())
print(f'Total time: {time.time() - start:.2f}s')

5.3 事件循环

事件循环是 asyncio 的核心,负责调度和执行协程。

import asyncio

async def task1():
    print('Task 1 starting')
    await asyncio.sleep(2)
    print('Task 1 done')

async def task2():
    print('Task 2 starting')
    await asyncio.sleep(1)
    print('Task 2 done')

# 获取当前事件循环
async def main():
    loop = asyncio.get_running_loop()
    print(f'Event loop: {loop}')
    
    # 在事件循环中调度任务
    task1_handle = loop.create_task(task1())
    task2_handle = loop.create_task(task2())
    
    await task1_handle
    await task2_handle

asyncio.run(main())

5.4 异步上下文管理器和迭代器

异步上下文管理器(async with)

import asyncio

class AsyncResource:
    async def __aenter__(self):
        print('Acquiring resource')
        await asyncio.sleep(1)
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print('Releasing resource')
        await asyncio.sleep(1)
    
    async def query(self):
        print('Querying resource')
        return 'result'

async def main():
    async with AsyncResource() as resource:
        result = await resource.query()
        print(f'Got: {result}')

asyncio.run(main())

异步迭代器(async for)

import asyncio

class AsyncCounter:
    def __init__(self, max_count):
        self.max_count = max_count
        self.count = 0
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.count >= self.max_count:
            raise StopAsyncIteration
        
        await asyncio.sleep(0.5)
        self.count += 1
        return self.count

async def main():
    async for number in AsyncCounter(5):
        print(f'Number: {number}')

asyncio.run(main())

5.5 异步 I/O 实战

异步 HTTP 请求(使用 aiohttp)

import asyncio
import aiohttp

async def fetch_url(session, url):
    """异步获取 URL"""
    async with session.get(url) as response:
        content = await response.text()
        return len(content)

async def main():
    urls = [
        'https://www.python.org',
        'https://www.github.com',
        'https://www.stackoverflow.com'
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        
        for url, size in zip(urls, results):
            print(f'{url}: {size} bytes')

# asyncio.run(main())  # 需要安装 aiohttp

异步文件操作(使用 aiofiles)

import asyncio
import aiofiles

async def read_file(filename):
    """异步读取文件"""
    async with aiofiles.open(filename, 'r') as f:
        content = await f.read()
        return content

async def write_file(filename, content):
    """异步写入文件"""
    async with aiofiles.open(filename, 'w') as f:
        await f.write(content)

async def main():
    # 并发读取多个文件
    files = ['file1.txt', 'file2.txt', 'file3.txt']
    contents = await asyncio.gather(*[read_file(f) for f in files])
    
    # 并发写入
    await asyncio.gather(*[
        write_file(f'output_{f}', content) 
        for f, content in zip(files, contents)
    ])

# asyncio.run(main())  # 需要安装 aiofiles

5.6 处理阻塞代码

asyncio 提供了在协程中运行阻塞代码的机制。

import asyncio
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def blocking_io():
    """阻塞式 I/O 操作"""
    time.sleep(2)
    return 'IO Result'

def cpu_intensive():
    """CPU 密集型任务"""
    return sum(i * i for i in range(10**7))

async def main():
    loop = asyncio.get_running_loop()
    
    # 在线程池中运行阻塞 I/O
    result = await loop.run_in_executor(
        ThreadPoolExecutor(), 
        blocking_io
    )
    print(f'IO result: {result}')
    
    # 在进程池中运行 CPU 密集型任务
    result = await loop.run_in_executor(
        ProcessPoolExecutor(),
        cpu_intensive
    )
    print(f'CPU result: {result}')

asyncio.run(main())

六、并发模型选择指南

6.1 任务类型分析

ScreenShot_2025-10-17_100156_914.png

6.2 性能对比实验

import time
import threading
import multiprocessing
import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

# CPU 密集型任务
def cpu_bound_task(n):
    return sum(i * i for i in range(n))

# I/O 密集型任务
def io_bound_task(delay):
    time.sleep(delay)
    return f'Slept {delay}s'

async def async_io_task(delay):
    await asyncio.sleep(delay)
    return f'Slept {delay}s'

def benchmark_cpu_bound():
    """CPU 密集型任务性能测试"""
    n = 10**7
    tasks = 4
    
    # 单线程
    start = time.time()
    for _ in range(tasks):
        cpu_bound_task(n)
    print(f'Sequential: {time.time() - start:.2f}s')
    
    # 多线程(受 GIL 限制)
    start = time.time()
    with ThreadPoolExecutor(max_workers=4) as executor:
        list(executor.map(cpu_bound_task, [n] * tasks))
    print(f'Threading: {time.time() - start:.2f}s')
    
    # 多进程(绕过 GIL)
    start = time.time()
    with ProcessPoolExecutor(max_workers=4) as executor:
        list(executor.map(cpu_bound_task, [n] * tasks))
    print(f'Multiprocessing: {time.time() - start:.2f}s')

def benchmark_io_bound():
    """I/O 密集型任务性能测试"""
    delay = 1
    tasks = 10
    
    # 单线程
    start = time.time()
    for _ in range(tasks):
        io_bound_task(delay)
    print(f'Sequential: {time.time() - start:.2f}s')
    
    # 多线程
    start = time.time()
    with ThreadPoolExecutor(max_workers=10) as executor:
        list(executor.map(io_bound_task, [delay] * tasks))
    print(f'Threading: {time.time() - start:.2f}s')
    
    # asyncio
    async def run_async():
        await asyncio.gather(*[async_io_task(delay) for _ in range(tasks)])
    
    start = time.time()
    asyncio.run(run_async())
    print(f'Asyncio: {time.time() - start:.2f}s')

if __name__ == '__main__':
    print('=== CPU Bound Benchmark ===')
    benchmark_cpu_bound()
    
    print('\n=== I/O Bound Benchmark ===')
    benchmark_io_bound()

6.3 最佳实践建议

选择决策树

  1. 任务是 CPU 密集型吗?
    • 是 → 使用 multiprocessing
    • 否 → 继续判断
  2. 任务是 I/O 密集型吗?
    • 是 → 需要大量并发(1000+)?
      • 是 → 使用 asyncio
      • 否 → 使用 threading
    • 否 → 考虑单线程优化
  3. 需要混合处理吗?
    • 是 → asyncio + run_in_executor

常见场景推荐

场景推荐方案原因
Web 爬虫asyncio + aiohttp大量网络请求,高并发
图像处理multiprocessingCPU 密集,需要并行
数据库操作threadingI/O 等待,中等并发
API 服务器asyncio(FastAPI)高并发,异步I/O
科学计算multiprocessingCPU 密集,数值计算
文件转换multiprocessing + Pool批量处理,CPU 密集

七、常见陷阱与调试技巧

7.1 竞态条件(Race Condition)

import threading

# 错误示例:存在竞态条件
counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1  # 非原子操作

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

print(f'Expected: 1000000, Got: {counter}')  # 结果不确定

# 正确做法:使用锁
counter = 0
lock = threading.Lock()

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

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

print(f'With lock: {counter}')  # 结果确定

7.2 死锁(Deadlock)

import threading
import time

# 错误示例:可能导致死锁
lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1():
    with lock1:
        print('Thread 1 acquired lock1')
        time.sleep(0.1)
        with lock2:  # 等待 lock2
            print('Thread 1 acquired lock2')

def thread2():
    with lock2:
        print('Thread 2 acquired lock2')
        time.sleep(0.1)
        with lock1:  # 等待 lock1
            print('Thread 2 acquired lock1')

# 正确做法:统一加锁顺序
def safe_thread1():
    with lock1:
        with lock2:
            print('Safe thread 1 executed')

def safe_thread2():
    with lock1:  # 相同的顺序
        with lock2:
            print('Safe thread 2 executed')

7.3 调试技巧

import logging
import asyncio
import threading

# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(threadName)s - %(message)s'
)

# 线程调试
def debug_thread():
    thread_id = threading.get_ident()
    logging.debug(f'Thread ID: {thread_id}')

# asyncio 调试
async def debug_coroutine():
    # 启用调试模式
    loop = asyncio.get_running_loop()
    loop.set_debug(True)
    
    # 记录慢回调
    loop.slow_callback_duration = 0.1
    
    await asyncio.sleep(0.2)  # 会被记录为慢回调

# 使用 asyncio.run 的调试模式
# asyncio.run(debug_coroutine(), debug=True)

八、性能优化技巧

8.1 减少上下文切换

# 使用进程/线程池而非频繁创建
from concurrent.futures import ThreadPoolExecutor

# 不推荐:频繁创建线程
def bad_practice():
    for _ in range(100):
        t = threading.Thread(target=some_task)
        t.start()
        t.join()

# 推荐:使用线程池
def good_practice():
    with ThreadPoolExecutor(max_workers=10) as executor:
        executor.map(some_task, range(100))

8.2 选择合适的进程启动方法

import multiprocessing as mp

# spawn:更安全但启动慢
mp.set_start_method('spawn')

# fork:快速但可能有问题(仅 Unix)
mp.set_start_method('fork')

# forkserver:折中方案
mp.set_start_method('forkserver')

8.3 使用 numba 或 Cython 优化

# 使用 numba 加速 CPU 密集型代码
from numba import jit

@jit(nopython=True)
def fast_computation(n):
    result = 0
    for i in range(n):
        result += i * i
    return result

# 比纯 Python 快几十倍
result = fast_computation(10**7)

参考文献

  1. Real Python - What Is the Python Global Interpreter Lock (GIL)?
    深入解释 GIL 的工作原理和影响
    realpython.com/python-gil/
  2. Python Official Documentation - multiprocessing
    官方文档,multiprocessing 模块的完整 API 参考
    docs.python.org/3/library/m…
  3. Python Official Documentation - threading
    官方文档,threading 模块的详细说明
    docs.python.org/3/library/t…
  4. Python Official Documentation - asyncio
    官方文档,asyncio 异步编程框架
    docs.python.org/3/library/a…
  5. PEP 703 - Making the Global Interpreter Lock Optional in CPython
    移除 GIL 的官方提案
    peps.python.org/pep-0703/
  6. Wikipedia - Global Interpreter Lock
    GIL 的技术背景和跨语言对比
    en.wikipedia.org/wiki/Global…
  7. Real Python - Speed Up Your Python Program With Concurrency
    实用的并发编程指南和性能优化建议
    realpython.com/python-conc…
  8. Super Fast Python - Multiprocessing Pool: The Complete Guide
    进程池的详细使用教程
    superfastpython.com/multiproces…
  9. GeeksforGeeks - Multiprocessing in Python Set 2
    进程间通信机制详解
    www.geeksforgeeks.org/python/mult…
  10. Stack Overflow - Multiprocessing vs Multithreading vs Asyncio
    社区讨论三种并发模型的适用场景
    stackoverflow.com/questions/2…