2. 线程池应用

概念

频繁创建线程会占用栈内存,也会让调度开销压过任务本身。线程池事先准备若干工人线程,任务放进队列,空闲工人取走执行。 池子要限制同时运行的线程数和排队长度。队列满了必须有明确策略:拒绝、由提交者自己跑,或阻塞提交方。任务全部提交后要 shutdown,并等待已提交任务结束,否则进程可能停不下来。

  • 核心参数:核心线程数、最大线程数、空闲回收时间、工作队列、拒绝策略。
  • Java 常用 ThreadPoolExecutor;不要在生产代码里使用无界的 Executors.newCachedThreadPool 承接外部请求。
  • Python 用 concurrent.futures.ThreadPoolExecutor。I/O 型任务适合线程池,CPU 密集计算更适合进程池。
  • submit 得到的是未来结果。调用方可以用 Future.get / future.result 取回返回值或异常。

算法流程

下面的 UML 图说明 Java 例程和 Python 例程共同实现的流程。实线箭头表示调用或发送,虚线箭头表示返回或唤起。

提交任务(UML 时序图)

sequenceDiagram participant Caller as 提交线程 participant Pool as 线程池 participant Queue as 有界队列 participant Worker as 工人线程 Caller->>Pool: submit(task) alt 有空闲核心线程 Pool->>Worker: 立即执行 else 核心线程都忙且队列未满 Pool->>Queue: 任务入队 Worker->>Queue: 空闲后取走执行 else 队列已满且未到最大线程数 Pool->>Worker: 创建非核心线程执行 else 队列已满且线程数已满 Pool->>Caller: 拒绝策略,提交者自己执行 end Caller->>Pool: shutdown,不再接收新任务 Pool-->>Caller: awaitTermination,等待已提交任务结束

Java 例程

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PoolDemo {
    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                                      // 核心线程:平时保留 2 个工人
                4,                                      // 最大线程:忙时最多再扩到 4 个
                30, TimeUnit.SECONDS,                   // 超出核心数的空闲线程,30 秒后回收
                new ArrayBlockingQueue<Runnable>(8),    // 有界队列,最多再排 8 个任务
                new ThreadPoolExecutor.CallerRunsPolicy()); // 队列也满了,就由提交任务的线程自己执行
        for (int i = 0; i < 6; i++) {
            final int n = i;                            // 匿名类要捕获的变量必须是 final
            pool.submit(new Runnable() {                // submit 只是入队,不表示已经开始跑
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " task " + n);
                }
            });
        }
        pool.shutdown();                                // 不再接收新任务,已提交的还会跑完
        pool.awaitTermination(5, TimeUnit.SECONDS);     // 主线程最多再等 5 秒
    }
}

Python 例程

from concurrent.futures import ThreadPoolExecutor

def work(n):
    return n * n          # 线程池里的任务可以有返回值

# with 结束时会 shutdown(wait=True),等已提交任务完成再退出
with ThreadPoolExecutor(max_workers=4, thread_name_prefix="worker") as pool:
    # submit 立即返回 Future,任务在池内线程中执行
    futures = [pool.submit(work, i) for i in range(6)]
    for fut in futures:
        print(fut.result())   # 任务没完就阻塞在这里;任务抛异常也会从这里重新抛出