返回Notes

/ notes

关于并发编程(3)

线程池参数/流程/调优/状态,以及 Go 的 Goroutine、Channel、Select、GMP

线程池与 Go 并发

ThreadPoolExecutor

ThreadPoolExecutor 是 Java 线程池的核心实现,用于复用线程、控制并发度、缓冲任务并提供过载保护。

七个核心参数

  1. corePoolSize:核心线程数;
  2. maximumPoolSize:允许的最大线程数;
  3. keepAliveTime:非核心线程空闲存活时间;
  4. unit:存活时间单位;
  5. workQueue:等待执行任务的阻塞队列;
  6. threadFactory:创建线程,可统一命名、设置异常处理器;
  7. handler:拒绝策略。

任务执行流程

提交任务后按以下顺序处理:

  1. 当前工作线程数小于 corePoolSize:创建核心线程执行任务;
  2. 否则尝试将任务放入 workQueue
  3. 队列已满且工作线程数小于 maximumPoolSize:创建非核心线程;
  4. 队列已满且线程数达到上限:执行拒绝策略。

线程池不是“线程数未达到最大值就直接建线程”;它会优先使用核心线程,再入队,队列满后才扩容。

常用阻塞队列

  • ArrayBlockingQueue:有界数组队列,容量固定;
  • LinkedBlockingQueue:链表队列,可有界;默认构造容量很大,使用时建议显式设定上限;
  • SynchronousQueue:不缓存任务,任务必须直接交给空闲线程;
  • PriorityBlockingQueue:按优先级出队;
  • DelayQueue:延迟到期后才能取出。

拒绝策略

  • AbortPolicy:抛出 RejectedExecutionException,默认策略;
  • CallerRunsPolicy:由提交任务的线程执行,形成反压;
  • DiscardPolicy:直接丢弃;
  • DiscardOldestPolicy:丢弃队列中最旧任务后重试提交。

业务中通常建议自定义拒绝策略:记录指标、告警、按业务决定降级或持久化任务,避免静默丢失。

线程池大小配置

  • CPU 密集型:线程数通常设为 CPU 核数附近,如 NcpuNcpu + 1,避免过多上下文切换。
  • I/O 密集型:可配置更多线程以覆盖 I/O 等待;不能机械地固定为 CPU 核数两倍,应根据任务计算/等待比例、下游容量、队列堆积和压测结果确定。

常用估算:线程数 ≈ Ncpu × (1 + 等待时间 / 计算时间),仅作起点,最终以监控和压测为准。

建议按任务类型隔离线程池,避免慢 I/O 任务挤占 CPU 任务资源。

常见 Executors 工厂方法

  • newFixedThreadPool:固定线程数,但默认使用无界队列;
  • newCachedThreadPool:线程数可快速膨胀,不适合无边界流量;
  • newScheduledThreadPool:定时与周期任务;
  • newSingleThreadExecutor:单线程顺序执行。

生产环境优先显式构造 ThreadPoolExecutor,指定有界队列、线程名和拒绝策略,而非直接使用默认 Executors 工厂方法。

线程池关闭与状态

  • shutdown():不再接收新任务,继续执行队列中的已提交任务;
  • shutdownNow():不再接收新任务,尝试中断执行中的任务,并返回队列中未执行任务;中断是否生效取决于任务是否正确响应中断。

线程池状态:

  • RUNNING:接收新任务并处理队列任务;
  • SHUTDOWN:不接收新任务,继续处理队列任务;
  • STOP:不接收、不处理队列任务,尝试中断工作线程;
  • TIDYING:任务已终止,准备执行终止钩子;
  • TERMINATED:彻底终止。

线程池使用要点

  1. 使用自定义、有边界的线程池;
  2. 明确任务类型与超时策略,避免在线程池中无限阻塞;
  3. 为线程设置有意义的名称,配置异常处理和监控;
  4. 监控活跃线程数、队列长度、任务耗时、拒绝次数;
  5. 防止任务互相等待造成线程池饥饿;
  6. 关闭应用时优雅停止线程池。

Go 并发编程

Go 并发的三项基础:

  • Goroutine:轻量级执行单元,通过 go 启动;
  • Channel:Goroutine 间传递数据与同步;
  • select:同时监听多个 Channel 操作。
select {
case result := <-resultCh:
    fmt.Println(result)
case <-ctx.Done():
    return ctx.Err()
}

GMP 调度模型

  • G(Goroutine):待执行的协程;
  • M(Machine):操作系统线程;
  • P(Processor):执行 Go 代码所需的逻辑处理器,数量通常由 GOMAXPROCS 控制。

调度器将 G 分配给持有 P 的 M 执行,并通过本地队列、全局队列和工作窃取提高吞吐。Goroutine 遇到阻塞、系统调用或网络 I/O 时,运行时会尽量调度其他 G 继续运行。

Go 并发实践

  • 使用 context.Context 传递取消、超时和请求范围的元数据;
  • 用 Channel 或 sync.Mutex 明确共享数据的访问方式;
  • 启动 Goroutine 后必须考虑退出路径,避免泄漏;
  • 使用 sync.WaitGroup 等待一组任务结束;
  • 使用 go test -race 检测数据竞争。