线程池的自发性死锁问题

2021/12/25 HPCThread

在我的个人项目中发现 tbb 多进程的 CPU 使用率始终无法满载运行,让人不爽,突然一日在阅读他人项目的时候,发现了问题所在。

# 什么是自发性死锁

如果存在一个异步任务 A ,发起了异步任务 BB 又发起了 C,...,直到第 N 个异步任务。这 N 个异步任务占满了线程池里的所有线程,这时候第 N 个异步任务发起的新任务或者从其他地方发起的新任务都将堵死在任务队列中,等待线程进行处理,而这时已经没有空闲的线程了,都已经阻塞在前 N 个异步任务里。

这是一种线程池自发性死锁案例,其实,线程池的自发性死锁触发没这么苛刻,只要满足一个要求即可:当前处理的异步任务发起新的异步任务,而新的异步任务阻塞在任务队列里。 在并发比较高的情形下,线程池的自发性死锁问题就不能忽视了。

::: tips 每个 CPU 都会维持一个运行队列,理想情况下,调度器会不断让队列中的进程运行。进程不是处在 sleep 状态就是 run able 状态。如果 CPU 过载,就会出现调度器跟不上系统的要求,导致可运行的进程会填满队列。队列愈大,程序执行时间就愈长。 :::

# cpu 无法满载运行的问题

在并发比较高的情形下,cpu 没有满载运行。问题排查之下,发现还是和线程池的自发性死锁有关,只是线程池还没死锁,但是线程池已经出现了以下情形:当前处理的异步任务发起新的异步任务,而新的异步任务阻塞在任务队列里。 相当于直接废掉了一个线程!

换句话说,假如当前系统有 8 核,线程池默认开启 9 个线程,原则上跑满 9 个线程就可以让 cpu 满载。但是,其中 4 个异步任务发起了新的异步任务,而新的异步任务阻塞在任务队列,原来的 4 个异步任务直接让线程阻塞了,线程被调度到内核阻塞队列等待唤醒。这一段时间,系统内只有 5 个线程在进行异步任务处理,cpu 只能跑到 50% 左右。更关键的是,异步任务既然发起了新的异步任务,那大概率不会只发起一个,反而是多个异步任务扔到任务队列里。结果就是,原来的异步任务就会一直阻塞线程,导致线程池里的线程根本无法跑满,cpu 远远达不到 100%

# 参考

ThreadPool - Crazing (opens new window)

Last Updated: 2023-10-29T08:26:04.000Z