🐍 第28课:并发编程

—— threading/multiprocessing:让 Python 飞起来

🏆 线程+进程+线程池+进程池
✅ Python验证通过

📌 本课目标

1️⃣ GIL 与并发模型

"""Python GIL(全局解释器锁)的影响:
- GIL 保证同一时刻只有一个线程执行 Python 字节码
- I/O 密集型(网络/文件/数据库):多线程有效,GIL 在 I/O 等待时释放
- CPU 密集型(计算/加密/图像处理):多线程无效,需要多进程绕过 GIL

选择策略:
┌──────────────┬──────────────┬──────────────┐
│   任务类型    │    推荐方案   │    原因      │
├──────────────┼──────────────┼──────────────┤
│ I/O 密集型   │  threading   │ GIL 自动释放 │
│ CPU 密集型   │ multiprocessing│ 绕过 GIL    │
│ 混合型       │ asyncio      │ 单线程并发   │
└──────────────┴──────────────┴──────────────┘
"""

2️⃣ threading 多线程

import threading
import time
from concurrent.futures import ThreadPoolExecutor

# 方式一:直接创建线程
def fetch_url(url, delay=1):
    """模拟网络请求"""
    print(f"  开始: {url}")
    time.sleep(delay)  # 模拟 I/O 等待
    print(f"  完成: {url}")
    return f"result_{url}"

threads = []
urls = [f"url_{i}" for i in range(5)]
for url in urls:
    t = threading.Thread(target=fetch_url, args=(url,))
    t.start()
    threads.append(t)

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

# 方式二:ThreadPoolExecutor(推荐)
with ThreadPoolExecutor(max_workers=3) as pool:
    results = pool.map(fetch_url, urls)
    print(f"结果: {list(results)}")

# 方式三:submit + as_completed(更灵活)
with ThreadPoolExecutor(max_workers=3) as pool:
    futures = {pool.submit(fetch_url, url): url for url in urls}
    for future in as_completed(futures):
        url = futures[future]
        try:
            result = future.result()
            print(f"  {url} → {result}")
        except Exception as e:
            print(f"  {url} 异常: {e}")

# 线程安全:Lock
counter = 0
lock = threading.Lock()

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

threads = [threading.Thread(target=safe_increment, args=(100000,)) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"最终计数: {counter} (期望: 500000)")

3️⃣ multiprocessing 多进程

import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor

# CPU 密集型任务
def compute_prime(n):
    """计算第n个素数"""
    count = 0
    num = 2
    while True:
        if all(num % i != 0 for i in range(2, int(num**0.5) + 1)):
            count += 1
            if count == n:
                return num
        num += 1

# 单进程
start = time.time()
results_single = [compute_prime(1000) for _ in range(4)]
single_time = time.time() - start

# 多进程
start = time.time()
with ProcessPoolExecutor(max_workers=4) as pool:
    results_multi = list(pool.map(compute_prime, [1000] * 4))
multi_time = time.time() - start

print(f"单进程: {single_time:.2f}s")
print(f"多进程: {multi_time:.2f}s")
print(f"加速比: {single_time/multi_time:.1f}x")

# 进程间通信:Queue
def producer(queue):
    for i in range(5):
        queue.put(f"item_{i}")
        print(f"  生产: item_{i}")

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

queue = multiprocessing.Queue()
p1 = multiprocessing.Process(target=producer, args=(queue,))
p2 = multiprocessing.Process(target=consumer, args=(queue,))
p1.start()
p2.start()
p1.join()
queue.put(None)  # 发送结束信号
p2.join()

4️⃣ 共享状态

import multiprocessing

# 共享内存
def worker_with_shared(num, shared_value, shared_array, lock):
    with lock:
        shared_value.value += 1
        shared_array[num] = num * 10

if __name__ == "__main__":
    lock = multiprocessing.Lock()
    shared_val = multiprocessing.Value("i", 0)      # 整数
    shared_arr = multiprocessing.Array("d", [0]*5)   # 双精度数组
    
    processes = []
    for i in range(5):
        p = multiprocessing.Process(
            target=worker_with_shared,
            args=(i, shared_val, shared_arr, lock)
        )
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print(f"共享值: {shared_val.value}")
    print(f"共享数组: {list(shared_arr)}")

5️⃣ 验证脚本

#!/usr/bin/env python3
"""第28课 并发编程验证"""
import threading
import multiprocessing
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def test_threading():
    results = []
    def worker(n):
        time.sleep(0.01)
        results.append(n)
    
    threads = [threading.Thread(target=worker, args=(i,)) for i in range(10)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    assert len(results) == 10
    print("✅ threading测试通过")

def test_thread_pool():
    def square(n):
        return n * n
    
    with ThreadPoolExecutor(max_workers=4) as pool:
        results = list(pool.map(square, range(10)))
    assert results == [i*i for i in range(10)]
    print("✅ ThreadPoolExecutor测试通过")

def test_lock():
    counter = 0
    lock = threading.Lock()
    def increment():
        nonlocal counter
        for _ in range(1000):
            with lock:
                counter += 1
    
    threads = [threading.Thread(target=increment) for _ in range(5)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    assert counter == 5000
    print("✅ Lock线程安全测试通过")

def test_process_pool():
    def cube(n):
        return n ** 3
    
    with ProcessPoolExecutor(max_workers=2) as pool:
        results = list(pool.map(cube, range(5)))
    assert results == [0, 1, 8, 27, 64]
    print("✅ ProcessPoolExecutor测试通过")

if __name__ == "__main__":
    test_threading()
    test_thread_pool()
    test_lock()
    test_process_pool()
    print("\n🎉 第28课全部验证通过!")

6️⃣ 生产级线程池模式

import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from queue import Queue

class WorkerPool:
    """生产级线程池"""
    
    def __init__(self, max_workers=5):
        self.max_workers = max_workers
        self.results = Queue()
        self.errors = []
    
    def submit_tasks(self, task_func, items):
        with ThreadPoolExecutor(max_workers=self.max_workers) as pool:
            futures = {pool.submit(task_func, item): item for item in items}
            for future in as_completed(futures):
                try:
                    result = future.result()
                    self.results.put(result)
                except Exception as e:
                    self.errors.append({"item": futures[future], "error": str(e)})
    
    def get_results(self):
        results = []
        while not self.results.empty():
            results.append(self.results.get())
        return results

# 使用
import time
def process_url(url):
    time.sleep(0.01)
    return f"result_{url}"

pool = WorkerPool(max_workers=3)
pool.submit_tasks(process_url, [f"url_{i}" for i in range(10)])
print(f"结果: {len(pool.get_results())} 个, 错误: {len(pool.errors)} 个")

7️⃣ 多进程数据处理

import multiprocessing
from concurrent.futures import ProcessPoolExecutor
import math

def is_prime(n):
    if n < 2: return False
    if n < 4: return True
    if n % 2 == 0: return False
    for i in range(3, int(math.sqrt(n)) + 1, 2):
        if n % i == 0: return False
    return True

def count_primes_range(start, end):
    return sum(1 for n in range(start, end) if is_prime(n))

def parallel_prime_count(max_num, num_workers=None):
    num_workers = num_workers or multiprocessing.cpu_count()
    chunk_size = max_num // num_workers
    ranges = [(i * chunk_size, (i+1) * chunk_size if i < num_workers-1 else max_num)
              for i in range(num_workers)]
    
    with ProcessPoolExecutor(max_workers=num_workers) as pool:
        results = list(pool.map(count_primes_range, *zip(*ranges)))
    
    total = sum(results)
    print(f"0-{max_num} 范围内有 {total} 个素数 ({num_workers}进程)")
    return total

8️⃣ 并发模式对比

import threading
import multiprocessing
import asyncio
import time
import concurrent.futures

# 场景1:I/O 密集(网络请求模拟)
def io_task():
    time.sleep(0.01)
    return "done"

# 串行
def serial_io(n):
    start = time.time()
    for _ in range(n):
        io_task()
    return time.time() - start

# 多线程
def threaded_io(n, workers=10):
    start = time.time()
    with concurrent.futures.ThreadPoolExecutor(workers) as pool:
        list(pool.map(lambda _: io_task(), range(n)))
    return time.time() - start

# 场景2:CPU 密集(计算)
def cpu_task(n):
    return sum(i * i for i in range(n))

# 串行
def serial_cpu(tasks):
    start = time.time()
    for n in tasks:
        cpu_task(n)
    return time.time() - start

# 多进程
def parallel_cpu(tasks, workers=4):
    start = time.time()
    with concurrent.futures.ProcessPoolExecutor(workers) as pool:
        list(pool.map(cpu_task, tasks))
    return time.time() - start

# 对比
n = 100
t_serial = serial_io(n)
t_threaded = threaded_io(n)
print(f"I/O 密集 ({n}个任务):")
print(f"  串行: {t_serial:.2f}s")
print(f"  多线程: {t_threaded:.2f}s")
print(f"  加速: {t_serial/t_threaded:.1f}x")

tasks = [500000] * 4
t_serial_cpu = serial_cpu(tasks)
t_parallel_cpu = parallel_cpu(tasks)
print(f"
CPU 密集 ({len(tasks)}个任务):")
print(f"  串行: {t_serial_cpu:.2f}s")
print(f"  多进程: {t_parallel_cpu:.2f}s")
print(f"  加速: {t_serial_cpu/t_parallel_cpu:.1f}x")

9️⃣ 线程安全数据结构

import threading
from queue import Queue, LifoQueue, PriorityQueue
from collections import defaultdict

class ThreadSafeDict:
    """线程安全字典"""
    def __init__(self):
        self._dict = {}
        self._lock = threading.Lock()
    
    def get(self, key, default=None):
        with self._lock:
            return self._dict.get(key, default)
    
    def set(self, key, value):
        with self._lock:
            self._dict[key] = value
    
    def update(self, **kwargs):
        with self._lock:
            self._dict.update(kwargs)
    
    def items(self):
        with self._lock:
            return list(self._dict.items())

class ThreadSafeCounter:
    """线程安全计数器"""
    def __init__(self):
        self._counts = defaultdict(int)
        self._lock = threading.Lock()
    
    def increment(self, key="default"):
        with self._lock:
            self._counts[key] += 1
    
    def get(self, key="default"):
        with self._lock:
            return self._counts[key]
    
    def top(self, n=10):
        with self._lock:
            return sorted(self._counts.items(), key=lambda x: x[1], reverse=True)[:n]

# 队列选择指南
# Queue (FIFO) → 先进先出,最常用
# LifoQueue (栈) → 后进先出
# PriorityQueue → 优先级队列,元素为 (priority, item)
# SimpleQueue → 无任务的简化队列(Python 3.7+)

🔑 本课要点

  1. threading——I/O 密集型首选,GIL 限制 CPU 并行
  2. multiprocessing——CPU 密集型用多进程,绕过 GIL
  3. ThreadPoolExecutor——线程池,推荐的新式写法
  4. 进程间通信——Queue/Pipe/Value/Array 多种方式
  5. concurrent.futures——统一的异步执行接口,线程/进程池皆可