python从入门到精通-第7章: 并发与异步 — GIL的现实与突破

8 阅读33分钟

第7章: 并发与异步 — GIL的现实与突破

Java/Kotlin 开发者对并发的理解建立在"线程 = OS线程"这个基本事实上:Thread.start() 直接映射 pthread_createsynchronized 直接映射 futex,JVM 的内存模型(JMM)虽然复杂,但底层模型是清晰的。Python 的并发世界则被一个名为 GIL(Global Interpreter Lock,全局解释器锁) 的东西彻底改变——它让多线程在 CPU 密集型场景下几乎毫无用处,迫使你转向多进程或异步协程。这不是 Python 的"缺陷",而是一个有意识的设计取舍。理解 GIL 是理解 Python 并发的一切起点。


7.1 GIL 详解: 什么是GIL,为什么存在,如何影响你

Java/Kotlin 对比

// Java: 线程直接映射 OS 线程,无全局锁
// 4 个线程在 4 核 CPU 上真正并行执行 CPU 密集任务
public class CpuBound {
    public static void main(String[] args) throws InterruptedException {
        int numThreads = 4;
        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                long sum = 0;
                for (int j = 0; j < 100_000_000; j++) {
                    sum += j;
                }
                System.out.println(sum);
            });
            threads[i].start();
        }
        for (Thread t : threads) t.join();
        // 4 核上耗时 ≈ 单线程的 1/4
    }
}
// Kotlin: 协程是用户态轻量线程,但底层仍依赖 JVM 线程
// dispatchers.default 上的 CPU 密集任务会占用真实线程
import kotlinx.coroutines.*

fun main() = runBlocking {
    val jobs = List(4) {
        launch(Dispatchers.Default) {
            var sum = 0L
            repeat(100_000_000) { sum += it }
            println(sum)
        }
    }
    jobs.joinAll()
    // Dispatchers.Default 线程数 = CPU 核数,真正并行
}

Python 实现

# === GIL 的本质 ===
# CPython 的引用计数(reference counting)是线程不安全的。
# 如果没有 GIL,两个线程同时对同一个对象做 refcount++,
# 可能导致引用计数丢失,对象被过早回收。
# GIL 是一把互斥锁,确保同一时刻只有一个线程执行 Python 字节码。

import threading
import time

# === Demo: GIL 对 CPU 密集型任务的影响 ===

def cpu_bound(n: int) -> int:
    """纯 CPU 计算,GIL 不会被释放"""
    total = 0
    for i in range(n):
        total += i
    return total


def run_sequential(n: int, workers: int) -> float:
    """顺序执行"""
    start = time.perf_counter()
    for _ in range(workers):
        cpu_bound(n)
    return time.perf_counter() - start


def run_threads(n: int, workers: int) -> float:
    """多线程执行 — 受 GIL 限制,不会更快"""
    start = time.perf_counter()
    threads = []
    for _ in range(workers):
        t = threading.Thread(target=cpu_bound, args=(n,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    return time.perf_counter() - start


if __name__ == "__main__":
    N = 20_000_000
    WORKERS = 4

    seq_time = run_sequential(N, WORKERS)
    thread_time = run_threads(N, WORKERS)

    print(f"顺序执行:   {seq_time:.3f}s")
    print(f"多线程执行: {thread_time:.3f}s")
    print(f"加速比:     {seq_time / thread_time:.2f}x")
    # 典型输出(4核):
    # 顺序执行:   3.2s
    # 多线程执行: 3.5s  ← 不仅没快,反而更慢(GIL 切换开销)
    # 加速比:     0.91x
# === Demo: GIL 对 I/O 密集型任务无影响 ===
import threading
import time
import urllib.request

def fetch_url(url: str) -> str:
    """I/O 操作会主动释放 GIL"""
    with urllib.request.urlopen(url, timeout=5) as resp:
        return resp.read(100).decode()[:50]


def run_sequential_io(urls: list[str]) -> float:
    start = time.perf_counter()
    for url in urls:
        fetch_url(url)
    return time.perf_counter() - start


def run_threads_io(urls: list[str]) -> float:
    start = time.perf_counter()
    threads = [
        threading.Thread(target=fetch_url, args=(url,))
        for url in urls
    ]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return time.perf_counter() - start


if __name__ == "__main__":
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
    ]
    seq = run_sequential_io(urls)
    thr = run_threads_io(urls)
    print(f"顺序: {seq:.2f}s, 多线程: {thr:.2f}s")
    # 顺序: 3.0s, 多线程: 1.0s ← I/O 密集型多线程有效

核心差异

维度Java/KotlinPython (CPython)
线程模型1:1(线程 = OS线程)1:1,但受 GIL 约束
CPU 并行多线程即可必须用多进程
I/O 并发多线程/协程多线程/协程均可
内存共享天然共享,需同步同进程内共享,跨进程需 IPC
锁粒度对象级(synchronized全局(GIL)+ 对象级(Lock

GIL 何时释放

  • I/O 操作socket.read()urllib、文件读写等
  • time.sleep():显式让出 GIL
  • C 扩展:NumPy、Pandas 等在计算前显式释放 GIL(Py_BEGIN_ALLOW_THREADS
  • 字节码边界:默认每执行 100 条字节码(sys.setswitchinterval() 可调)检查是否切换

常见陷阱

# 陷阱1: 以为多线程能加速 CPU 计算
def bad_cpu_parallelism():
    # 这不会比单线程快,反而更慢
    threads = [threading.Thread(target=cpu_bound, args=(10_000_000,)) for _ in range(8)]
    for t in threads: t.start()
    for t in threads: t.join()

# 陷阱2: 忽略 GIL 切换间隔的影响
import sys
# 默认 5ms 切换一次,对于细粒度操作可能频繁切换
print(sys.getswitchinterval())  # 0.005

# 陷阱3: 以为 Lock 能绕过 GIL
lock = threading.Lock()
def increment():
    with lock:  # Lock 保护的是 Python 对象的一致性,不是 CPU 并行
        global counter
        counter += 1  # 仍然受 GIL 约束,只是防止了竞态条件

何时使用

  • 需要 CPU 并行multiprocessing(7.3节)或 ProcessPoolExecutor(7.4节)
  • I/O 并发且代码简单threading(7.2节)
  • I/O 并发且追求高吞吐asyncio(7.5节)
  • 混合型 → CPU 部分用进程池,I/O 部分用线程/协程

7.2 threading 模块: I/O 密集型的利器

Java/Kotlin 对比

// Java: Thread 是一等公民,直接映射 OS 线程
Thread t = new Thread(() -> {
    System.out.println("Running in: " + Thread.currentThread().getName());
});
t.setDaemon(true);  // 守护线程
t.start();
t.join();           // 等待完成

// 同步原语
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

// Java 的 CountDownLatch ≈ Python 的 Event
CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await();

// Java 的 Semaphore
Semaphore sem = new Semaphore(3);
sem.acquire();
try { /* ... */ } finally { sem.release(); }
// Kotlin: 协程是更推荐的方式,但也可以用线程
import kotlin.concurrent.thread

thread(name = "worker") {
    println("Running in: ${Thread.currentThread().name}")
}

// Kotlin 协程(更轻量)
import kotlinx.coroutines.*

// launch = 不返回结果的协程(类似 Thread)
// async = 返回 Deferred 的协程(类似 Future)

Python 实现

import threading
import time
import random

# === 1. Thread 创建和启动 ===

def worker(name: str, seconds: int):
    print(f"[{threading.current_thread().name}] {name} 开始工作")
    time.sleep(seconds)  # I/O 操作,释放 GIL
    print(f"[{threading.current_thread().name}] {name} 完成")


# 方式1: 直接创建 Thread 对象
t1 = threading.Thread(target=worker, args=("Alice", 2), name="worker-1")
t1.start()
t1.join()

# 方式2: 继承 Thread 类
class MyWorker(threading.Thread):
    def __init__(self, name: str, seconds: int):
        super().__init__(name=name)
        self.seconds = seconds

    def run(self):
        worker(self.name, self.seconds)


t2 = MyWorker("Bob", 1)
t2.start()
t2.join()

# === 2. daemon 线程 ===
# daemon=True 的线程在主线程结束时自动终止(不等待完成)
# 类似 Java 的 Thread.setDaemon(true)

def background_task():
    for i in range(10):
        print(f"background: {i}")
        time.sleep(0.5)


daemon = threading.Thread(target=background_task, daemon=True)
daemon.start()
time.sleep(1.2)
# 主线程结束,daemon 线程被强制终止(不会打印完 10 次)
print("主线程结束")

# === 3. Lock ===
# 与 Java ReentrantLock 类似

counter = 0
lock = threading.Lock()

def safe_increment(n: int):
    global counter
    for _ in range(n):
        with lock:  # 自动获取/释放,类似 Java 的 synchronized 块
            counter += 1


threads = [threading.Thread(target=safe_increment, args=(100_000,)) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(f"counter = {counter}")  # 400000(无锁则结果不确定)

# === 4. RLock (可重入锁) ===
# 同一个线程可以多次 acquire 不会死锁,类似 Java ReentrantLock

rlock = threading.RLock()

def outer():
    with rlock:
        print("outer acquired")
        inner()  # 同一线程再次获取,不会死锁

def inner():
    with rlock:
        print("inner acquired")

outer()

# === 5. Event ===
# 类似 Java CountDownLatch(1),用于线程间信号通知

event = threading.Event()

def waiter(name: str):
    print(f"{name}: 等待信号...")
    event.wait()  # 阻塞直到 event.set()
    print(f"{name}: 收到信号,继续执行")


def setter():
    time.sleep(1)
    print("发送信号")
    event.set()  # 唤醒所有等待的线程


threading.Thread(target=waiter, args=("W1",)).start()
threading.Thread(target=waiter, args=("W2",)).start()
threading.Thread(target=setter).start()

# === 6. Condition ===
# 类似 Java 的 wait()/notifyAll()

condition = threading.Condition()
items: list[str] = []

def producer():
    for i in range(3):
        time.sleep(random.random())
        with condition:
            items.append(f"item-{i}")
            print(f"生产: item-{i}")
            condition.notify()  # 唤醒一个消费者

def consumer(name: str):
    with condition:
        while not items:
            condition.wait()  # 释放锁并等待
        item = items.pop(0)
        print(f"{name} 消费: {item}")


threading.Thread(target=producer).start()
threading.Thread(target=consumer, args=("C1",)).start()
threading.Thread(target=consumer, args=("C2",)).start()

# === 7. Semaphore ===
# 限制同时访问资源的线程数

semaphore = threading.Semaphore(2)  # 最多 2 个线程同时执行

def limited_worker(name: str):
    with semaphore:
        print(f"{name}: 获得许可")
        time.sleep(2)
        print(f"{name}: 释放许可")


for i in range(5):
    threading.Thread(target=limited_worker, args=(f"W{i}",)).start()

核心差异

概念JavaPython
创建线程new Thread(runnable)threading.Thread(target=fn)
守护线程setDaemon(true)daemon=True
互斥锁ReentrantLockthreading.Lock() / RLock()
等待/通知wait()/notify()Condition.wait()/notify()
信号量Semaphorethreading.Semaphore()
倒计时门栓CountDownLatchthreading.Event()(简化版)

关键差异:Python 的 threading 模块没有 CountDownLatchCyclicBarrierReadWriteLock 等高级同步原语。Event 是最接近 CountDownLatch(1) 的替代,但功能更简单。需要更复杂的协调时,通常直接用 asyncio

常见陷阱

# 陷阱1: 忘记 join(),主线程提前结束
threads = [threading.Thread(target=worker, args=("x", 1)) for _ in range(5)]
for t in threads: t.start()
# 没有 join()!主线程直接结束,daemon 线程被杀死

# 陷阱2: 用 Lock 代替 RLock 导致死锁
lock = threading.Lock()
def recursive():
    with lock:  # 第二次获取同一把锁 → 死锁!
        recursive()

# 陷阱3: Condition.wait() 不在 with 块中
condition = threading.Condition()
# condition.wait()  # RuntimeError: 不能在未获取锁时调用
# 正确: with condition: condition.wait()

# 陷阱4: 线程间共享可变对象没有同步
shared_list = []
def append_item():
    shared_list.append(1)  # list.append 是原子操作,但...
    # 如果是 shared_list.extend(other_list) 就不是原子的

何时使用

  • 网络请求、文件读写等 I/O 密集型任务threading 是最简单的选择
  • 需要与同步 C 扩展交互threading(C 扩展可能释放 GIL)
  • CPU 密集型 → 不要用 threading,用 multiprocessing
  • 高并发 I/O(数千连接) → 用 asyncio,线程开销太大

7.3 multiprocessing: CPU 密集型的解决方案

Java/Kotlin 对比

// Java: ProcessBuilder 创建独立 JVM 进程
ProcessBuilder pb = new ProcessBuilder("java", "-jar", "worker.jar");
Process process = pb.start();
int exitCode = process.waitFor();

// 进程间通信通常通过:文件、socket、HTTP、消息队列
// Java 没有内置的跨进程共享内存 API(需要 JNI 或第三方库)
// Kotlin Native: 可以创建真正的多进程
// 但 Kotlin/JVM 与 Java 相同,依赖 ProcessBuilder

// Kotlin 协程在 JVM 上仍然是单进程多线程
// 不能绕过 JVM 的线程模型

Python 实现

import multiprocessing
import time

# === 1. Process 创建 ===
# 与 threading.Thread API 几乎一致,但每个 Process 是独立 OS 进程

def cpu_worker(name: str, n: int) -> int:
    """CPU 密集型任务 — 在独立进程中运行,不受 GIL 限制"""
    total = 0
    for i in range(n):
        total += i
    print(f"[{name}] 完成, 结果: {total}")
    return total


def demo_process():
    processes = []
    for i in range(4):
        # API 与 threading.Thread 几乎一样
        p = multiprocessing.Process(
            target=cpu_worker,
            args=(f"Worker-{i}", 20_000_000),
        )
        processes.append(p)
        p.start()

    for p in processes:
        p.join()
        print(f"{p.name} exit code: {p.exitcode}")


if __name__ == "__main__":
    # Windows 上必须加这个保护,否则子进程会递归导入
    demo_process()

# === 2. 进程间通信: Queue ===

def producer(queue: multiprocessing.Queue):
    for i in range(5):
        queue.put(f"message-{i}")
        print(f"生产: message-{i}")
    queue.put(None)  # 哨兵值,通知消费者结束


def consumer(queue: multiprocessing.Queue, name: str):
    while True:
        msg = queue.get()
        if msg is None:
            print(f"{name}: 收到结束信号")
            queue.put(None)  # 传给下一个消费者
            break
        print(f"{name}: 消费 {msg}")


def demo_queue():
    q = multiprocessing.Queue()
    p1 = multiprocessing.Process(target=producer, args=(q,))
    p2 = multiprocessing.Process(target=consumer, args=(q, "C1"))
    p3 = multiprocessing.Process(target=consumer, args=(q, "C2"))

    p2.start()
    p3.start()
    p1.start()

    p1.join()
    p2.join()
    p3.join()


# === 3. 进程间通信: Pipe ===

def pipe_demo():
    parent_conn, child_conn = multiprocessing.Pipe()

    def child(conn):
        conn.send("来自子进程的问候")
        conn.send(42)
        conn.close()

    p = multiprocessing.Process(target=child, args=(child_conn,))
    p.start()

    print(parent_conn.recv())  # 来自子进程的问候
    print(parent_conn.recv())  # 42
    p.join()

# Pipe vs Queue: Pipe 是点对点的(2个端点),Queue 是多生产者多消费者

# === 4. 共享内存: Value, Array ===

def shared_memory_demo():
    # Value: 共享单个值
    counter = multiprocessing.Value("i", 0)  # "i" = signed int
    # Array: 共享数组
    arr = multiprocessing.Array("d", [0.0, 0.0, 0.0])  # "d" = double

    def increment(value, n):
        for _ in range(n):
            with value.get_lock():  # 必须加锁!跨进程没有 GIL 保护
                value.value += 1

    processes = [
        multiprocessing.Process(target=increment, args=(counter, 100_000))
        for _ in range(4)
    ]
    for p in processes: p.start()
    for p in processes: p.join()
    print(f"共享计数器: {counter.value}")  # 400000

# === 5. Manager(更灵活的共享) ===

def manager_demo():
    with multiprocessing.Manager() as manager:
        # Manager 通过代理对象实现跨进程共享
        shared_dict = manager.dict()
        shared_list = manager.list()

        def worker(d, lst, idx):
            d[f"key-{idx}"] = f"value-{idx}"
            lst.append(f"item-{idx}")

        processes = [
            multiprocessing.Process(target=worker, args=(shared_dict, shared_list, i))
            for i in range(4)
        ]
        for p in processes: p.start()
        for p in processes: p.join()

        print(f"共享字典: {dict(shared_dict)}")
        print(f"共享列表: {list(shared_list)}")
    # 注意: Manager 有性能开销(通过 IPC 序列化),不如 Value/Array 高效

# === 6. 进程池 Pool ===

def pool_demo():
    def square(n: int) -> int:
        return n * n

    # 创建进程池(默认大小 = CPU 核数)
    with multiprocessing.Pool(processes=4) as pool:
        # map: 类似 Java Stream 的并行 map
        results = pool.map(square, range(10))
        print(f"map 结果: {results}")  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

        # map_async: 非阻塞版本
        async_result = pool.map_async(square, range(10))
        # ... 做其他事情 ...
        results = async_result.get()  # 阻塞等待结果
        print(f"map_async 结果: {results}")

        # apply: 提交单个任务
        result = pool.apply(square, (5,))
        print(f"apply 结果: {result}")  # 25

        # imap: 惰性版本,按序返回
        for r in pool.imap(square, range(5)):
            print(f"imap: {r}", end=" ")
        print()

    # Pool 本质上就是 Java ExecutorService 的进程版
    # 但 API 设计更函数式(map/reduce 风格)

核心差异

维度Java ProcessBuilderPython multiprocessing
创建开销启动新 JVM(秒级)fork() 或 spawn(毫秒级)
IPC 机制socket/文件/外部 MQQueue/Pipe/Value/Array/Manager
共享内存无内置支持Value/Array(高效)、Manager(灵活)
进程池无内置(需线程池或框架)Pool 内置
序列化无需(独立进程)pickle 序列化参数和返回值

关键差异:Python 的 multiprocessing 比 Java 的 ProcessBuilder 高级得多——内置了进程池、共享内存、多种 IPC 机制。这是因为 Python 需要 multiprocessing 来弥补 GIL 的不足,所以标准库提供了完整的多进程支持。

常见陷阱

# 陷阱1: Windows 上缺少 if __name__ == "__main__" 保护
# Windows 使用 spawn 模式(重新导入模块),不加保护会递归创建子进程
# Linux/macOS 默认用 fork,通常不会出问题,但写上保护是好习惯

# 陷阱2: 传递不可 pickle 的对象
import multiprocessing

def work(q):
    pass

# q = multiprocessing.Queue()  # Queue 可以在进程间传递
# 但 lambda、文件对象、socket 等不能 pickle
# multiprocessing.Process(target=lambda: None)  # PicklingError!

# 陷阱3: Manager 的性能陷阱
# Manager 的每次属性访问都经过 IPC,非常慢
# 如果只是共享简单数值,用 Value/Array 而不是 Manager().dict()

# 陷阱4: 忘记 Pool.close() 或使用 with 语句
pool = multiprocessing.Pool(4)
# pool.map(...)  # 用完不关闭 → 资源泄漏
# 正确: 用 with 语句自动管理

何时使用

  • CPU 密集型并行计算multiprocessing.Pool 是首选
  • 需要绕过 GIL 的计算任务multiprocessing.Process
  • 需要跨进程共享状态Value/Array(简单)、Manager(灵活但慢)
  • I/O 密集型 → 不需要 multiprocessingthreadingasyncio 更轻量

7.4 concurrent.futures: 统一的执行器接口

Java/Kotlin 对比

// Java: ExecutorService 是并发的核心抽象
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> future = executor.submit(() -> {
    Thread.sleep(1000);
    return 42;
});
int result = future.get();       // 阻塞等待
boolean done = future.isDone();  // 非阻塞检查

// CompletableFuture(Java 8+): 链式异步
CompletableFuture.supplyAsync(() -> fetchData())
    .thenApply(data -> process(data))
    .thenAccept(result -> save(result))
    .exceptionally(ex -> { handleError(ex); return null; });

// 等待多个 Future
List<Future<?>> futures = ...;
executor.invokeAll(futures);  // 等待全部完成
// Kotlin: 协程 + Dispatchers 是更现代的方式
val result = withContext(Dispatchers.Default) {
    // 自动调度到线程池
    heavyComputation()
}

// async/await
val deferred = async { fetchData() }
val result = deferred.await()

Python 实现

import concurrent.futures
import time
import urllib.request

# === 1. ThreadPoolExecutor ===
# 等价于 Java Executors.newFixedThreadPool()

def fetch_url(url: str) -> tuple[str, int]:
    """模拟网络请求"""
    try:
        with urllib.request.urlopen(url, timeout=10) as resp:
            return url, resp.status
    except Exception as e:
        return url, -1


def thread_pool_demo():
    urls = [f"https://httpbin.org/get?id={i}" for i in range(6)]

    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # submit: 提交单个任务,返回 Future
        future_to_url = {
            executor.submit(fetch_url, url): url
            for url in urls
        }

        # as_completed: 按完成顺序获取结果(不等全部完成)
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                _, status = future.result()  # 阻塞获取结果
                print(f"{url}{status}")
            except Exception as e:
                print(f"{url} → 错误: {e}")

    # map: 更简洁的批量提交(按输入顺序返回结果)
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        results = executor.map(fetch_url, urls)
        for url, status in results:
            print(f"{url}{status}")


# === 2. ProcessPoolExecutor ===
# 等价于 Java ExecutorService + 独立进程
# 用于 CPU 密集型任务

def cpu_task(n: int) -> int:
    total = 0
    for i in range(n):
        total += i
    return total


def process_pool_demo():
    numbers = [10_000_000, 20_000_000, 15_000_000, 25_000_000]

    with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
        # 与 ThreadPoolExecutor API 完全一致!
        futures = [executor.submit(cpu_task, n) for n in numbers]

        for future in concurrent.futures.as_completed(futures):
            result = future.result()
            print(f"结果: {result}")

        # map 版本
        results = list(executor.map(cpu_task, numbers))
        print(f"map 结果: {results}")


# === 3. Future 对象 ===
# 等价于 Java Future,但功能更丰富

def future_demo():
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        future = executor.submit(lambda: time.sleep(1) or "done")

        print(f"是否完成: {future.done()}")       # False
        print(f"是否取消: {future.cancelled()}")   # False

        # future.cancel()  # 尝试取消(如果还没开始执行)
        # future.add_done_callback(fn)  # 完成时回调

        result = future.result(timeout=5)  # 带超时的阻塞等待
        print(f"结果: {result}")  # done


# === 4. wait: 等待多个 Future ===

def wait_demo():
    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
        futures = {
            executor.submit(time.sleep, i): f"task-{i}"
            for i in [1, 2, 3, 0.5]
        }

        # FIRST_COMPLETED: 任一完成即返回
        # FIRST_EXCEPTION: 第一个异常即返回
        # ALL_COMPLETED: 全部完成(默认)
        done, not_done = concurrent.futures.wait(
            futures,
            timeout=2,
            return_when=concurrent.futures.FIRST_COMPLETED,
        )
        print(f"已完成: {len(done)}, 未完成: {len(not_done)}")


if __name__ == "__main__":
    print("=== ThreadPoolExecutor ===")
    thread_pool_demo()
    print("\n=== ProcessPoolExecutor ===")
    process_pool_demo()

核心差异

概念JavaPython
执行器接口ExecutorServiceExecutor(抽象基类)
线程池Executors.newFixedThreadPool()ThreadPoolExecutor
进程池无内置ProcessPoolExecutor
FutureFuture<T> / CompletableFutureFuture
链式回调thenApply/thenComposeadd_done_callback(无链式)
等待多个invokeAll()wait() / as_completed()

关键差异:Python 的 concurrent.futures API 是直接从 Java 的 ExecutorService 借鉴来的。最大的缺失是没有 CompletableFuture 那样的链式组合能力——Python 用 asyncio 来填补这个位置。

常见陷阱

# 陷阱1: ProcessPoolExecutor 中传递不可 pickle 的对象
# 与 multiprocessing 相同的限制
# executor.submit(lambda: 42)  # PicklingError!

# 陷阱2: 忘记 with 语句,线程池/进程池不关闭
executor = concurrent.futures.ThreadPoolExecutor(4)
# executor.submit(...)  # 用完不关闭 → 程序不会退出
# 正确: 用 with 或手动 executor.shutdown(wait=True)

# 陷阱3: Future.result() 在回调中再次调用
def callback(future):
    # future.result()  # 如果任务抛异常,这里会重新抛出
    pass

# 陷阱4: as_completed 返回顺序不确定
# 如果需要按提交顺序获取结果,用 executor.map()

何时使用

  • 需要简洁的线程/进程池 APIconcurrent.futures 是最佳选择
  • I/O 密集型批量任务ThreadPoolExecutor
  • CPU 密集型批量任务ProcessPoolExecutor
  • 需要链式异步组合asyncioconcurrent.futures 不支持)
  • 简单并行 mapexecutor.map() 最简洁

7.5 asyncio: 事件循环与协程

Java/Kotlin 对比

// Kotlin 协程: 结构化并发,suspend 函数
import kotlinx.coroutines.*

suspend fun fetchData(): String {
    delay(1000)  // 挂起,不阻塞线程
    return "data"
}

fun main() = runBlocking {  // 事件循环入口
    // launch: 启动协程(不返回结果)
    launch {
        val data = fetchData()
        println(data)
    }

    // async: 启动协程(返回 Deferred ≈ Future)
    val deferred = async { fetchData() }
    println(deferred.await())  // 等待结果

    // 并发执行
    val results = awaitAll(
        async { fetchData() },
        async { fetchData() },
    )
}
// Java: 没有语言级协程(Project Loom 的虚拟线程是另一条路线)
// Java 21+ Virtual Threads:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> f = executor.submit(() -> {
        Thread.sleep(1000);  // 真正的阻塞,但虚拟线程很轻量
        return "data";
    });
    f.get();
}
// Virtual Threads ≠ 协程。它们是轻量线程,不是协作式调度。

Python 实现

import asyncio
import time

# === 1. 事件循环: asyncio.run() ===
# 等价于 Kotlin 的 runBlocking

async def main():
    print("hello")
    await asyncio.sleep(1)
    print("world")

# asyncio.run(main())  # 启动事件循环,运行 main(),然后关闭循环
# 注意: asyncio.run() 是 3.7+ 的 API

# === 2. 协程: async def / await ===
# async def 定义协程函数(类似 Kotlin 的 suspend fun)
# await 挂起协程(类似 Kotlin 的 suspend 调用)

async def fetch_data(name: str, delay_sec: float) -> str:
    """模拟异步 I/O 操作"""
    print(f"{name}: 开始获取数据")
    await asyncio.sleep(delay_sec)  # 挂起,不阻塞事件循环
    print(f"{name}: 数据获取完成")
    return f"{name}-result"


async def basic_demo():
    # 顺序执行(没有并发)
    r1 = await fetch_data("A", 1)
    r2 = await fetch_data("B", 1)
    print(f"顺序耗时: ~2s, 结果: {r1}, {r2}")


# === 3. Task 和 asyncio.create_task() ===
# Task 是协程的包装,调度到事件循环上并发执行
# 类似 Kotlin 的 launch { }

async def task_demo():
    # 创建 Task(立即调度,不等待完成)
    task1 = asyncio.create_task(fetch_data("A", 1))
    task2 = asyncio.create_task(fetch_data("B", 1))

    # 等待两个任务完成
    r1 = await task1
    r2 = await task2
    print(f"并发耗时: ~1s, 结果: {r1}, {r2}")


# === 4. asyncio.gather() 并发执行 ===
# 类似 Kotlin 的 awaitAll()
# 更简洁地并发执行多个协程

async def gather_demo():
    # gather 并发执行,按传入顺序返回结果
    results = await asyncio.gather(
        fetch_data("A", 1),
        fetch_data("B", 1),
        fetch_data("C", 1),
    )
    print(f"并发耗时: ~1s, 结果: {results}")
    # 结果: ('A-result', 'B-result', 'C-result')


# === 5. asyncio.sleep() vs time.sleep() ===
# 这是初学者最容易犯的错误

async def sleep_comparison():
    print("--- time.sleep (阻塞事件循环) ---")
    start = time.perf_counter()
    await asyncio.sleep(1)  # 正确: 挂起协程,事件循环可以处理其他任务
    print(f"asyncio.sleep 耗时: {time.perf_counter() - start:.2f}s")

    # time.sleep(1)  # 错误! 会阻塞整个事件循环,其他协程无法运行
    # 在协程中永远不要用 time.sleep(),用 asyncio.sleep()


async def wrong_sleep_demo():
    """演示 time.sleep 在协程中的危害"""
    async def good_task():
        await asyncio.sleep(1)
        return "good"

    async def bad_task():
        time.sleep(2)  # 阻塞整个事件循环 2 秒!
        return "bad"

    start = time.perf_counter()
    results = await asyncio.gather(good_task(), bad_task())
    elapsed = time.perf_counter() - start
    print(f"耗时: {elapsed:.2f}s")  # ~2s(而不是 2s)
    # bad_task 的 time.sleep 阻塞了事件循环,good_task 也被延迟


# === 完整运行示例 ===
async def full_demo():
    print("=== 基础协程 ===")
    await basic_demo()

    print("\n=== Task 并发 ===")
    await task_demo()

    print("\n=== gather 并发 ===")
    await gather_demo()

    print("\n=== sleep 对比 ===")
    await sleep_comparison()


if __name__ == "__main__":
    asyncio.run(full_demo())

asyncio 同步原语 vs Java 对应物

import asyncio

# === 1. asyncio.Queue: 异步队列 ===
# vs Java: BlockingQueue / Kotlin: Channel
async def producer(queue: asyncio.Queue):
    for i in range(5):
        await queue.put(f"item-{i}")
        print(f"生产: item-{i}")
        await asyncio.sleep(0.01)

async def consumer(queue: asyncio.Queue):
    while True:
        item = await queue.get()
        print(f"消费: {item}")
        queue.task_done()
        if item == "item-4":
            break

async def queue_demo():
    q = asyncio.Queue(maxsize=3)
    await asyncio.gather(producer(q), consumer(q))

asyncio.run(queue_demo())

# === 2. asyncio.Event: 事件通知 ===
# vs Java: CountDownLatch / Kotlin: CompletableDeferred
async def worker(event: asyncio.Event, worker_id: int):
    print(f"Worker-{worker_id}: 等待信号...")
    await event.wait()
    print(f"Worker-{worker_id}: 开始工作!")

async def event_demo():
    event = asyncio.Event()
    # 启动 3 个 worker
    workers = [asyncio.create_task(worker(event, i)) for i in range(3)]
    await asyncio.sleep(0.1)  # 确保 worker 都在等待
    event.set()  # 通知所有 worker
    await asyncio.gather(*workers)

asyncio.run(event_demo())

# === 3. asyncio.Semaphore: 并发限制 ===
# vs Java: Semaphore / Kotlin: Semaphore
async def api_call(sem: asyncio.Semaphore, url: str):
    async with sem:
        print(f"请求: {url}")
        await asyncio.sleep(0.1)  # 模拟网络请求
        return f"response from {url}"

async def semaphore_demo():
    # 限制最多 3 个并发请求
    sem = asyncio.Semaphore(3)
    urls = [f"/api/{i}" for i in range(10)]
    results = await asyncio.gather(*[api_call(sem, url) for url in urls])
    print(f"完成 {len(results)} 个请求")

asyncio.run(semaphore_demo())
原语Python asyncioJavaKotlin
互斥锁asyncio.LockReentrantLockMutex
信号量asyncio.SemaphoreSemaphoreSemaphore
事件asyncio.EventCountDownLatchCompletableDeferred
队列asyncio.QueueBlockingQueueChannel
条件asyncio.ConditionConditionCondition

核心差异

维度Kotlin 协程Python asyncio
定义suspend funasync def
挂起隐式(调用 suspend 函数)显式(await
启动launch { } / async { }asyncio.create_task()
等待多个awaitAll()asyncio.gather()
事件循环隐式(Dispatchers)显式(asyncio.run()
取消Job.cancel()Task.cancel()
结构化并发语言级保证3.11+ TaskGroup

关键差异

  1. Kotlin 的 suspend 是隐式的——调用 suspend 函数自动挂起。Python 的 await 是显式的——你必须写 await,否则协程不会执行。
  2. Kotlin 有结构化并发的语言级保证——coroutineScope 自动等待子协程。Python 3.11 之前没有这个保证(TaskGroup 弥补了这一点)。
  3. Python 的事件循环是显式的——asyncio.run() 创建并管理循环。Kotlin 的调度器更抽象。

常见陷阱

# 陷阱1: 忘记 await(最常见的错误!)
async def fetch():
    await asyncio.sleep(1)
    return "data"

async def bad():
    result = fetch()  # 错误!这只是一个协程对象,不会执行
    print(result)     # <coroutine object fetch at 0x...>
    # 正确: result = await fetch()

# 陷阱2: 在非 async 函数中调用 async 函数
# def normal_function():
#     await fetch()  # SyntaxError! await 只能在 async def 中使用

# 陷阱3: asyncio.run() 不能嵌套
# async def outer():
#     asyncio.run(inner())  # RuntimeError: This event loop is already running
#     # 正确: await inner()

# 陷阱4: 阻塞操作在协程中使用
# async def bad_io():
#     with open("large_file.txt") as f:  # 阻塞!
#         data = f.read()
#     # 正确: 使用 aiofiles 等异步库
#     # import aiofiles
#     # async with aiofiles.open("large_file.txt") as f:
#     #     data = await f.read()

何时使用

  • 高并发 I/O(HTTP 请求、数据库查询、WebSocket)asyncio 是最佳选择
  • 需要数千甚至数万并发连接asyncio(线程模型做不到)
  • 已有同步代码库threadingconcurrent.futures(改造成本低)
  • CPU 密集型multiprocessingasyncio 无法绕过 GIL)

7.6 async/await 深入

Java/Kotlin 对比

// Kotlin: 协程的高级模式
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.*

// 协程 Channel ≈ asyncio.Queue
val channel = Channel<String>()

// Mutex ≈ asyncio.Lock
val mutex = Mutex()
mutex.withLock { /* critical section */ }

// Semaphore
val semaphore = Semaphore(3)
semaphore.acquire()
try { /* ... */ } finally { semaphore.release() }

// Flow(异步流)≈ Python 的 async for + async generator
flow {
    for (i in 1..10) {
        delay(100)
        emit(i)
    }
}.collect { println(it) }

Python 实现

import asyncio

# === 1. async for: 异步迭代 ===
# 类似 Kotlin Flow 的 collect

async def async_range(n: int):
    """异步生成器: 逐个产出值,每次产出可以挂起"""
    for i in range(n):
        await asyncio.sleep(0.1)  # 模拟异步操作
        yield i


async def async_for_demo():
    print("--- async for ---")
    # async for 逐个消费异步生成器的值
    async for value in async_range(5):
        print(f"  收到: {value}")


# === 2. async with: 异步上下文管理器 ===
# 类似 Kotlin 的 use {} 但支持异步

class AsyncResource:
    """模拟异步资源(如数据库连接、HTTP 会话)"""

    async def __aenter__(self):
        print("  打开资源(异步)")
        await asyncio.sleep(0.1)  # 模拟异步打开
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("  关闭资源(异步)")
        await asyncio.sleep(0.1)  # 模拟异步关闭
        return False  # 不抑制异常


async def async_with_demo():
    print("--- async with ---")
    async with AsyncResource() as resource:
        print("  使用资源中...")
        await asyncio.sleep(0.2)


# === 3. asyncio.Queue: 异步队列 ===
# 协程安全的生产者-消费者模式

async def queue_demo():
    print("--- asyncio.Queue ---")
    queue: asyncio.Queue[str] = asyncio.Queue(maxsize=3)

    async def producer():
        for i in range(5):
            await asyncio.sleep(0.1)
            await queue.put(f"item-{i}")  # 队列满时自动挂起
            print(f"  生产: item-{i}")

    async def consumer(name: str):
        while True:
            item = await queue.get()  # 队列空时自动挂起
            print(f"  {name} 消费: {item}")
            queue.task_done()  # 标记处理完成

    # 启动生产者和消费者
    producers = [asyncio.create_task(producer()) for _ in range(2)]
    consumers = [asyncio.create_task(consumer(f"C{i}")) for i in range(2)]

    await asyncio.gather(*producers)
    await queue.join()  # 等待队列中所有项目被处理

    # 取消消费者(它们在无限循环中等待)
    for c in consumers:
        c.cancel()


# === 4. asyncio.Lock: 异步锁 ===
# 保护协程间的共享资源

async def lock_demo():
    print("--- asyncio.Lock ---")
    lock = asyncio.Lock()
    shared_counter = 0

    async def increment():
        nonlocal shared_counter
        async with lock:  # 获取锁,其他协程等待
            # 模拟一些操作
            temp = shared_counter
            await asyncio.sleep(0.001)  # 如果没有锁,这里可能产生竞态
            shared_counter = temp + 1

    tasks = [asyncio.create_task(increment()) for _ in range(100)]
    await asyncio.gather(*tasks)
    print(f"  计数器: {shared_counter}")  # 100


# === 5. asyncio.Semaphore: 异步信号量 ===
# 限制同时执行的协程数量

async def semaphore_demo():
    print("--- asyncio.Semaphore ---")
    sem = asyncio.Semaphore(2)  # 最多 2 个协程同时执行

    async def limited_task(name: str):
        async with sem:
            print(f"  {name}: 开始")
            await asyncio.sleep(1)
            print(f"  {name}: 完成")

    tasks = [asyncio.create_task(limited_task(f"T{i}")) for i in range(5)]
    await asyncio.gather(*tasks)
    # 5 个任务,每次最多 2 个并发,总耗时 ~3s


# === 完整运行 ===
async def full_demo():
    await async_for_demo()
    await async_with_demo()
    await queue_demo()
    await lock_demo()
    await semaphore_demo()


if __name__ == "__main__":
    asyncio.run(full_demo())

核心差异

概念KotlinPython asyncio
异步迭代Flow.collect {}async for
异步资源管理use {}(扩展函数)async with
异步队列Channelasyncio.Queue
异步锁Mutex.withLock {}async with asyncio.Lock()
异步信号量Semaphore.acquire()async with asyncio.Semaphore()
异步生成器flow { emit() }async def + yield

关键差异:Python 的异步原语(async forasync with)是语言级语法。Kotlin 的 FlowChannel 是库级抽象。Python 的方式更直接,Kotlin 的方式更灵活(可以组合各种操作符)。

常见陷阱

# 陷阱1: 在异步队列中使用 queue.get() 不 await
# item = queue.get()  # 错误!这是同步方法但会阻塞事件循环
# 正确: item = await queue.get()

# 陷阱2: 忘记 queue.task_done()
# async def consumer():
#     item = await queue.get()
#     # 忘记 task_done() → queue.join() 永远不会返回

# 陷阱3: 在 async with 中使用同步锁
# lock = threading.Lock()  # 错误!
# async with lock:  # TypeError
# 正确: lock = asyncio.Lock()

# 陷阱4: asyncio.Queue 不能跨进程使用
# 如果需要跨进程的异步队列,考虑用 multiprocessing.Queue + 线程桥接

何时使用

  • 流式数据处理async for + 异步生成器
  • 数据库连接、HTTP 会话管理async with
  • 生产者-消费者模式asyncio.Queue
  • 协程间共享资源保护asyncio.Lock
  • 限制并发数(如限流)asyncio.Semaphore

7.7 TaskGroup (3.11+) 与结构化并发

Java/Kotlin 对比

// Kotlin: 结构化并发是语言级保证
// coroutineScope 自动等待所有子协程

suspend fun structuredConcurrency() = coroutineScope {
    // 所有子协程必须在 coroutineScope 退出前完成
    launch {
        delay(1000)
        println("子任务1完成")
    }
    launch {
        delay(500)
        println("子任务2完成")
    }
    // coroutineScope 会等待两个 launch 完成
    // 如果任何一个子协程抛异常,所有子协程自动取消
}
// Java: 没有结构化并发(Project Loom 的 StructuredTaskScope 是预览特性)
// Java 21+ StructuredTaskScope:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<String> task1 = scope.fork(() -> fetchUser());
    Subtask<String> task2 = scope.fork(() -> fetchOrder());
    scope.join();
    scope.throwIfFailed();
    // 两个任务都成功
}

Python 实现

import asyncio

# === TaskGroup: 结构化并发 [3.11+] ===
# 等价于 Kotlin 的 coroutineScope
# 保证: 所有子任务在退出前完成,异常时自动取消所有子任务

async def taskgroup_demo():
    print("=== TaskGroup 基础 ===")

    async def worker(name: str, delay_sec: float) -> str:
        print(f"  {name}: 开始")
        await asyncio.sleep(delay_sec)
        print(f"  {name}: 完成")
        return f"{name}-result"

    # TaskGroup 自动管理子任务的生命周期
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(worker("A", 1))
        task2 = tg.create_task(worker("B", 0.5))
        task3 = tg.create_task(worker("C", 0.8))

    # 到这里,所有任务都已完成
    # 如果任何任务抛异常,其他任务被自动取消
    print(f"  结果: {task1.result()}, {task2.result()}, {task3.result()}")


# === TaskGroup 异常处理: 自动取消 ===

async def taskgroup_error_demo():
    print("\n=== TaskGroup 异常处理 ===")

    async def may_fail(name: str, fail: bool, delay: float):
        await asyncio.sleep(delay)
        if fail:
            raise ValueError(f"{name} 失败了!")
        print(f"  {name}: 成功")
        return f"{name}-ok"

    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(may_fail("A", False, 0.5))  # 会成功
            tg.create_task(may_fail("B", True, 0.3))   # 会失败
            tg.create_task(may_fail("C", False, 1.0))  # 会被取消
    except ValueError* as eg:  # 3.11+ except* 语法
        print(f"  捕获异常组: {eg}")
        for exc in eg.exceptions:
            print(f"    - {exc}")
    # 注意: B 失败后,C 被自动取消(即使 C 需要更长时间)
    # A 可能已经完成(因为它在 B 失败前就完成了)


# === TaskGroup vs 手动管理 ===

async def manual_approach():
    """3.11 之前的手动方式 — 容易出错"""
    tasks = []
    try:
        tasks.append(asyncio.create_task(asyncio.sleep(0.5)))
        tasks.append(asyncio.create_task(asyncio.sleep(0.3)))
        # 如果这里抛异常,tasks 中的协程不会被取消!
        results = await asyncio.gather(*tasks, return_exceptions=True)
    except Exception:
        # 需要手动取消所有任务
        for t in tasks:
            t.cancel()
        raise


async def taskgroup_approach():
    """3.11+ TaskGroup — 自动管理"""
    async with asyncio.TaskGroup() as tg:
        tg.create_task(asyncio.sleep(0.5))
        tg.create_task(asyncio.sleep(0.3))
        # 如果这里抛异常,TaskGroup 自动取消所有子任务


# === 收集 TaskGroup 的结果 ===

async def taskgroup_results():
    print("\n=== TaskGroup 收集结果 ===")

    results: list[str] = []

    async with asyncio.TaskGroup() as tg:
        for i in range(5):
            task = tg.create_task(asyncio.sleep(0.1 * i))
            # 用回调收集结果
            task.add_done_callback(
                lambda t, idx=i: results.append(f"task-{idx}")
            )

    print(f"  完成顺序: {results}")


if __name__ == "__main__":
    asyncio.run(taskgroup_demo())
    asyncio.run(taskgroup_error_demo())
    asyncio.run(taskgroup_results())

核心差异

维度Kotlin coroutineScopePython TaskGroup
结构化保证语言级库级(3.11+)
异常传播取消所有子协程取消所有子任务
结果收集List<Deferred>add_done_callback 或外部列表
超时withTimeout()asyncio.timeout() [3.11+]
嵌套天然支持天然支持(嵌套 async with

关键差异:Python 的 TaskGroup 是对 Kotlin coroutineScope 的直接借鉴,但实现更简单。Kotlin 的结构化并发是编译器保证的,Python 的 TaskGroup 是运行时保证的。

常见陷阱

# 陷阱1: 在 TaskGroup 外部访问未完成的任务
async def bad():
    async with asyncio.TaskGroup() as tg:
        task = tg.create_task(asyncio.sleep(1))
    # task.result()  # 安全,因为 TaskGroup 保证了所有任务完成

# 陷阱2: TaskGroup 中 create_task 的返回值赋值给外部变量
# 这是安全的,但要注意:如果 TaskGroup 因异常退出,变量可能未赋值
async def tricky():
    result = None
    try:
        async with asyncio.TaskGroup() as tg:
            result = await tg.create_task(some_task())
    except* Exception:
        if result is None:
            print("任务未完成")

# 陷阱3: 3.10 及以下没有 TaskGroup
# 需要用 asyncio.gather() + 手动取消作为替代

何时使用

  • Python 3.11+ 的所有新代码 → 优先使用 TaskGroup 替代手动任务管理
  • 需要"全部成功或全部回滚"的语义TaskGroup
  • 嵌套并发操作TaskGroup 天然支持嵌套
  • Python 3.10 → 用 asyncio.gather(return_exceptions=True) + 手动取消

7.8 ExceptionGroup 与 except* (3.11+, PEP 654)

Java/Kotlin 对比

// Java: 没有等价物
// Java 的异常是单线程的,一个方法只能抛一个异常
// ExecutorService.invokeAll() 中多个任务失败时,只抛第一个异常
// CompletableFuture.allOf() 同样只保留第一个异常
// 要收集所有异常,需要手动 try-catch 每个 Future

// Java 7+ 的 suppressed exceptions 是最接近的概念:
try {
    // ...
} catch (Exception e) {
    // e.getSuppressed() 可以获取被抑制的异常
    // 但这只适用于 try-with-resources,不适用于并发
}
// Kotlin: 同样没有等价物
// 协程中多个子任务失败时,只抛第一个异常
// kotlinx.coroutines 的 SupervisorJob 可以改变这个行为,
// 但仍然没有"多个异常聚合"的原生支持

Python 实现

import asyncio

# === 1. ExceptionGroup: 多个异常的聚合 ===
# 当多个并发任务同时失败时,Python 3.11+ 会将它们聚合为 ExceptionGroup

async def exceptiongroup_basic():
    print("=== ExceptionGroup 基础 ===")

    async def failing_task(name: str):
        await asyncio.sleep(0.1)
        raise ValueError(f"{name} 失败")

    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(failing_task("A"))
            tg.create_task(failing_task("B"))
            tg.create_task(failing_task("C"))
    except* ValueError as eg:
        # except* 是 3.11+ 新语法,专门用于匹配 ExceptionGroup
        print(f"  捕获到 {len(eg.exceptions)} 个 ValueError:")
        for exc in eg.exceptions:
            print(f"    - {exc}")

    # 输出:
    #   捕获到 3 个 ValueError:
    #     - A 失败
    #     - B 失败
    #     - C 失败


# === 2. except* 语法: 选择性处理 ===
# except* 可以只处理特定类型的异常,未处理的继续传播

async def except_star_demo():
    print("\n=== except* 选择性处理 ===")

    async def mixed_task(name: str, error_type: str):
        await asyncio.sleep(0.1)
        if error_type == "value":
            raise ValueError(f"{name}: ValueError")
        elif error_type == "type":
            raise TypeError(f"{name}: TypeError")
        elif error_type == "runtime":
            raise RuntimeError(f"{name}: RuntimeError")

    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(mixed_task("A", "value"))
            tg.create_task(mixed_task("B", "type"))
            tg.create_task(mixed_task("C", "value"))
            tg.create_task(mixed_task("D", "runtime"))
    except* ValueError as eg:
        print(f"  处理 ValueError: {len(eg.exceptions)} 个")
        for exc in eg.exceptions:
            print(f"    - {exc}")
    except* TypeError as eg:
        print(f"  处理 TypeError: {len(eg.exceptions)} 个")
        for exc in eg.exceptions:
            print(f"    - {exc}")
    # RuntimeError 没有被 except* 捕获,会继续传播
    # 如果没有外层处理,程序会崩溃


# === 3. 手动创建 ExceptionGroup ===

def manual_exceptiongroup():
    print("\n=== 手动创建 ExceptionGroup ===")

    errors = [
        ValueError("错误1"),
        TypeError("错误2"),
        RuntimeError("错误3"),
    ]

    eg = ExceptionGroup("多个错误", errors)
    print(f"  ExceptionGroup: {eg}")
    print(f"  包含 {len(eg.exceptions)} 个异常")

    # 用 except* 处理
    try:
        raise eg
    except* ValueError:
        print("  处理了 ValueError")
    except* (TypeError, RuntimeError):
        print("  处理了 TypeError 和 RuntimeError")


# === 4. ExceptionGroup 的嵌套 ===

async def nested_exceptiongroup():
    print("\n=== 嵌套 ExceptionGroup ===")

    async def outer_task(name: str):
        try:
            async with asyncio.TaskGroup() as tg:
                tg.create_task(asyncio.sleep(0.1))
                # 内层任务失败
                raise ValueError(f"{name}: 内层错误")
        except* ValueError as eg:
            # 内层已经处理,不会传播到外层
            print(f"  {name}: 内层处理了 {len(eg.exceptions)} 个异常")

    async def failing_outer():
        await asyncio.sleep(0.05)
        raise RuntimeError("外层错误")

    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(outer_task("A"))
            tg.create_task(outer_task("B"))
            tg.create_task(failing_outer())
    except* RuntimeError as eg:
        print(f"  外层捕获 RuntimeError: {eg.exceptions}")


# === 5. 实际应用: 批量操作的部分失败处理 ===

async def batch_operation(urls: list[str]) -> dict[str, str]:
    """模拟批量请求,部分失败不影响其他请求"""
    results: dict[str, str] = {}
    errors: list[Exception] = []

    async def fetch(url: str):
        await asyncio.sleep(0.1)
        if "bad" in url:
            raise ConnectionError(f"无法连接 {url}")
        return f"{url} 的内容"

    try:
        async with asyncio.TaskGroup() as tg:
            tasks = {tg.create_task(fetch(url)): url for url in urls}
    except* ConnectionError as eg:
        # 记录失败,但不中断
        errors.extend(eg.exceptions)

    # 收集成功的任务结果
    for task, url in tasks.items():
        if task.done() and not task.cancelled():
            try:
                results[url] = task.result()
            except Exception:
                pass  # 已经在 except* 中处理

    print(f"  成功: {len(results)}, 失败: {len(errors)}")
    return results


async def batch_demo():
    print("\n=== 批量操作部分失败 ===")
    urls = ["url-1", "url-2-bad", "url-3", "url-4-bad", "url-5"]
    results = await batch_operation(urls)
    print(f"  结果: {results}")


if __name__ == "__main__":
    asyncio.run(exceptiongroup_basic())
    asyncio.run(except_star_demo())
    manual_exceptiongroup()
    asyncio.run(nested_exceptiongroup())
    asyncio.run(batch_demo())

核心差异

维度Java/KotlinPython 3.11+
多异常聚合无原生支持ExceptionGroup
选择性处理except*
嵌套异常组天然支持
与并发集成TaskGroup 自动创建

关键差异ExceptionGroup + except* 是 Python 独有的特性,Java/Kotlin 没有等价物。这不是一个"对比"关系,而是 Python 在并发异常处理方面的创新。它解决了"多个并发任务同时失败时如何优雅处理"这个普遍问题。

常见陷阱

# 陷阱1: 用普通 except 代替 except*
try:
    raise ExceptionGroup("errors", [ValueError("a"), TypeError("b")])
except Exception as e:
    # 这能捕获,但 e 是 ExceptionGroup,不是 ValueError 或 TypeError
    # 你无法区分哪些异常是 ValueError,哪些是 TypeError
    print(type(e))  # <class 'ExceptionGroup'>

# 正确: 用 except* 分别处理
try:
    raise ExceptionGroup("errors", [ValueError("a"), TypeError("b")])
except* ValueError as eg:
    print(f"ValueError: {eg.exceptions}")
except* TypeError as eg:
    print(f"TypeError: {eg.exceptions}")

# 陷阱2: except* 中 raise 会创建新的 ExceptionGroup
try:
    raise ExceptionGroup("errors", [ValueError("a")])
except* ValueError:
    raise RuntimeError("处理失败")
# RuntimeError 会被包装成新的 ExceptionGroup

# 陷阱3: 3.10 及以下没有 except* 语法
# 需要手动处理: except BaseException as e → isinstance(e, ExceptionGroup)

何时使用

  • 并发任务可能部分失败except* 精确处理不同类型的异常
  • 批量操作(如批量 API 调用)ExceptionGroup 收集所有失败
  • 需要区分"已处理"和"未处理"的异常except* 的选择性处理
  • Python 3.10 及以下 → 用 return_exceptions=True + 手动检查

7.9 自由线程实验: PEP 703 (3.13)

Java/Kotlin 对比

// Java: 从第一天起就没有 GIL
// 多线程在 CPU 密集型任务上天然并行
// 这是 Java 开发者觉得 Python "奇怪"的根本原因

// Java 的代价: 需要显式同步(synchronized, volatile, Atomic*)
// Python 有 GIL 的好处: 不需要担心引用计数的线程安全
// Kotlin: 与 Java 相同,没有 GIL
// 但 Kotlin 的不可变 val 和 data class 减少了同步需求

Python 实现

# === PEP 703: Making the Global Interpreter Lock Optional in CPython ===
# Python 3.13 引入了实验性的 "free-threaded" 模式(no-GIL)
# 通过 python3.13t 可执行文件启用

# 注意: 这是一个实验性特性,截至 3.13 仍是预览状态
# 以下代码说明概念,在标准 CPython 3.10-3.12 上无法运行 no-GIL 模式

import threading
import time

# === 1. GIL vs no-GIL 的性能差异 ===

def cpu_bound_work(n: int) -> int:
    """纯 CPU 计算"""
    total = 0
    for i in range(n):
        total += i
    return total


def demo_gil_limitation():
    """演示 GIL 对 CPU 密集型的影响"""
    N = 10_000_000
    WORKERS = 4

    # 顺序执行
    start = time.perf_counter()
    for _ in range(WORKERS):
        cpu_bound_work(N)
    sequential_time = time.perf_counter() - start

    # 多线程执行(有 GIL)
    start = time.perf_counter()
    threads = [
        threading.Thread(target=cpu_bound_work, args=(N,))
        for _ in range(WORKERS)
    ]
    for t in threads: t.start()
    for t in threads: t.join()
    threaded_time = time.perf_counter() - start

    print(f"顺序执行:   {sequential_time:.2f}s")
    print(f"多线程(GIL): {threaded_time:.2f}s")
    print(f"加速比:     {sequential_time / threaded_time:.2f}x")
    # 在标准 CPython 上: 加速比 ≈ 1.0x(GIL 阻止并行)
    # 在 python3.13t 上:  加速比 ≈ 3.5x(4核,接近线性)


# === 2. no-GIL 模式下的新挑战 ===

# 在 no-GIL 模式下,Python 对象不再被 GIL 保护
# 你需要像 Java 一样处理线程安全

def demo_thread_safety_concerns():
    """
    在 no-GIL 模式下,这段代码有竞态条件:
    """
    counter = 0  # 不再被 GIL 保护!

    def increment():
        nonlocal counter
        for _ in range(100_000):
            # counter += 1 不再是线程安全的!
            # 等价于: temp = counter; temp += 1; counter = temp
            # 两个线程可能读到相同的 temp 值
            counter += 1

    # 在有 GIL 的 Python 中,counter += 1 是安全的(GIL 保证了原子性)
    # 在 no-GIL 的 Python 中,必须加锁:
    # lock = threading.Lock()
    # with lock: counter += 1


# === 3. 对 C 扩展的影响 ===

"""
no-GIL 模式对 C 扩展有重大影响:

1. 依赖 GIL 的 C 扩展需要修改:
   - 移除 Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS(不再需要释放 GIL)
   - 添加自己的线程安全机制

2. 使用引用计数的 C 代码需要修改:
   - Py_INCREF / Py_DECREF 不再是线程安全的
   - 需要使用新的线程安全引用计数 API

3. ABI 不兼容:
   - no-GIL 模式的 Python 有不同的 ABI
   - C 扩展需要为 no-GIL 模式单独编译
   - pip install 的预编译包可能不可用

4. 已适配的包:
   - NumPy, pandas 等核心科学计算库正在适配
   - 适配进度: https://github.com/python/cpython/issues/116165
"""


# === 4. 如何启用 no-GIL ===

"""
# 方法1: 使用 python3.13t 可执行文件
python3.13t my_script.py

# 方法2: 编译时禁用 GIL
./configure --disable-gil
make

# 方法3: 运行时检查
"""
import sys

def check_gil_status():
    """检查当前 Python 是否启用了 GIL"""
    if sys.version_info >= (3, 13):
        # 3.13+ 可以通过 sys._is_gil_enabled() 检查
        try:
            gil_enabled = sys._is_gil_enabled()
            print(f"GIL 状态: {'启用' if gil_enabled else '禁用'}")
        except AttributeError:
            print("GIL 状态: 启用(标准构建)")
    else:
        print(f"GIL 状态: 启用(Python {sys.version_info.major}.{sys.version_info.minor} 不支持禁用 GIL)")


# === 5. per-interpreter GIL (PEP 684, 3.12+) ===
# 另一个方向: 不是去掉 GIL,而是每个解释器实例有自己的 GIL

"""
import interpreters  # 3.12+ 实验性模块

# 创建独立的子解释器(有自己的 GIL)
interp = interpreters.create()

# 在子解释器中运行代码
interp.run('''
import threading
# 这个解释器有自己的 GIL,不影响主解释器
''')

# 这与 multiprocessing 不同:
# - 子解释器共享同一个进程(更轻量)
# - 但有独立的 GIL(不互相阻塞)
# - 目前仍有很多限制(不能共享大多数对象)
"""


if __name__ == "__main__":
    print("=== GIL 限制演示 ===")
    demo_gil_limitation()
    print("\n=== GIL 状态检查 ===")
    check_gil_status()

核心差异

维度Java/KotlinPython (有 GIL)Python (no-GIL, 3.13t)
CPU 并行天然支持需要多进程多线程即可
线程安全开发者负责GIL 保证开发者负责
同步需求必须大部分不需要必须
C 扩展无影响无影响需要重新适配
生态成熟度成熟成熟实验性

关键差异:no-GIL Python 让 Python 的线程模型向 Java/Kotlin 靠拢——获得了 CPU 并行能力,但也失去了 GIL 提供的"免费线程安全"。这是一个权衡,不是纯粹的升级。

常见陷阱

# 陷阱1: 以为 no-GIL 模式下所有代码自动变快
# no-GIL 只影响 CPU 密集型的多线程代码
# I/O 密集型代码不受影响(GIL 在 I/O 时本来就会释放)
# 单线程代码不受影响(甚至可能略慢,因为移除了 GIL 的优化)

# 陷阱2: 在 no-GIL 模式下忽略线程安全
# counter += 1  # 在 no-GIL 下有竞态条件!
# list.append()  # 在 no-GIL 下也可能不安全!
# 必须像写 Java 一样思考线程安全

# 陷阱3: 依赖 GIL 的第三方库可能不兼容
# 很多 C 扩展假设 GIL 存在
# 在 no-GIL 模式下可能崩溃或产生错误结果

# 陷阱4: 过早在生产中使用
# no-GIL 在 3.13 中仍是实验性的
# 生态系统适配需要时间(预计 3.15+ 才能稳定)

何时使用

  • 当前生产环境 → 不要使用 no-GIL,用 multiprocessing 绕过 GIL
  • 实验和评估 → 可以用 python3.13t 测试你的工作负载
  • CPU 密集型且进程间通信开销大 → no-GIL 是未来的解决方案
  • 依赖大量 C 扩展 → 等待生态系统适配(关注 NumPy、pandas 等的适配进度)

本章总结: Python 并发决策树

你的任务是什么类型?
│
├─ I/O 密集型(网络请求、文件、数据库)
│  ├─ 少量并发(< 100)→ threading 或 ThreadPoolExecutor
│  ├─ 大量并发(> 100)→ asyncio
│  └─ 已有同步代码库 → ThreadPoolExecutor(改造成本最低)
│
├─ CPU 密集型(计算、图像处理、机器学习)
│  ├─ 简单并行 map → ProcessPoolExecutor
│  ├─ 需要共享状态 → multiprocessing.Value/Array
│  └─ 大数据量 → 考虑 Dask、Ray 等分布式框架
│
├─ 混合型(CPU + I/O)
│  ├─ asyncio + ProcessPoolExecutor(推荐)
│  └─ threading + multiprocessing(更简单但不够优雅)
│
└─ 未来(Python 3.15+ no-GIL 稳定后)
   └─ 可能不再需要区分 CPU/I/O,多线程通吃

与 Java/Kotlin 的心智模型对照

你在 Java/Kotlin 中做的在 Python 中应该做的
new Thread() / thread {}threading.Thread()(I/O)或 multiprocessing.Process()(CPU)
ExecutorServiceconcurrent.futures.ThreadPoolExecutor(I/O)或 ProcessPoolExecutor(CPU)
CompletableFuture / async/awaitasyncio + async/await
coroutineScopeasyncio.TaskGroup [3.11+]
synchronized / ReentrantLockthreading.Lock()(线程)或 asyncio.Lock()(协程)
volatile / AtomicInteger通常不需要(GIL 保护),no-GIL 后需要
CountDownLatchthreading.Event()
Semaphorethreading.Semaphore()asyncio.Semaphore()
BlockingQueuequeue.Queue(线程)或 asyncio.Queue(协程)
多异常处理ExceptionGroup + except* [3.11+](Python 独有)