记一次线程池复用异常

记录下之前遇到的一个线上问题, 因为连续出了两次错.
线上发现协程执行任务全部阻塞, 数据库查询不出结果, 列表页白屏. profiler一看, 大量Dispather开头的线程全部是阻塞状态. 嗯, 64个Dispatcher.IO线程都被耗尽了.

定位到阻塞的地方

伪代码

  private suspend fun deal() = withContext(Dispatchers.IO) {
        while (flag) {
            try {
                val task = linkBlockingQueue.take()
                ....
             catch (e: InterruptedException) {
                Thread.currentThread().interrupt()
            }
                   

在页面销毁的时候把flag改成false, 妄图退出循环.
结果肯定是不行的, take()的时候线程已经阻塞, 根本没法重试while循环.

改成页面销毁的的时候强制interrupt线程, 问题是解决了.

新的问题

在下一个页面, 也有类似的在Dispatchers.IO里用blockingQuue.take()来阻塞的逻辑, 经常偶现刚take就失败.
看堆栈很明显, 线程在一开始take的时候就是interrupt状态.

肯定有人发现了, 上一个页面的catch里我们写了Thread.currentThread().interrupt(), 因为ctach住InterruptedException后, 线程的interrupt状态会被清空, 所以我们习惯在这里补上Thread.currentThread().interrupt(), 让调用者知道线程被interrupt了.

但是这个interrupt状态的线程, 回到了Dispatchers.IO线程池, 在接下来的任务中, 被复用到了, 所以一take, 就会马上报错. 之所以是偶现, 因为不一定能用到上个页面被interrupt的线程, 可能是池里别的线程.

所以在池化的线程里, 避免使用Thread.currentThread().interrupt(), 而是自己定义一个异常, 让调用者捕捉, 例如throw CancellationException("Operation interrupted", e)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。