浅记线程池模型中多个线程对同个fd接收缓冲区读取争夺的方案

Posted 狱典司

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅记线程池模型中多个线程对同个fd接收缓冲区读取争夺的方案相关的知识,希望对你有一定的参考价值。

浅记线程池模型中多个线程对同个同个fd接收缓冲区读取争夺的方案

问题描述

最近在复习实现和思考线程池的时候发现了一个问题:

概括说就是 线程池+多路IO(epoll ET)+ 非阻塞轮询处理数据的场景下,同个fd(socket)高频率接收数据,可能导致线程池中多个线程争夺读取该fd接收缓冲区的情况,怎么处理?

更详细的描述问题:
如果存在这样的情况,比如某个socket被恶意攻击,或者说数据接收频率很高(clients以高频率发数据包)
多路io每次返回都把任务放到任务队列,然后唤醒一个线程去取,假如线程1取到了任务,然后将自身标记为忙碌状态;
在线程1还在处理任务的时候,多路io再次在同一个fd上返回,再唤醒一个线程,线程里的线程2争夺到了该任务,但该任务仍然是读取与线程1相同的fd;
问题在于线程1此时还在处理它的任务,极端情况下(比如数据很长,读的时间比较久),线程2要开始读这个fd的接收缓冲区时,线程1也正在读这篇缓冲区,在这一部分没有线程间的同步机制保护的话,数据读取会产生混乱。

思考后得到如下的解决方案:

解决方案:

1. 维护额外的简易链接池,并结合互斥锁向外提供合法取fd的接口:

给开启的fd维护一个池,姑且算是一个简易的连接池,池的单元是结构体或者对象(这里统一用结构体吧),并用结构体内部的一个描述状态的变量(占用与否)来维护该连接池;
重点在于向外部提供一个访问(取)fd的函数或者方法,取的时候需要上锁后判断,函数退出前释放锁
通过这样的方式保证线程池里的线程不会争夺读取同一个fd(socket)的接收缓冲区数据。

2. 利用epoll在内核维护的监听红黑树来避免同一个fd的接收数据被同时划分为多个任务队列中的任务(更优)

总体的流程如下:
epoll_wait()返回 ----> 调用epoll_ctl()将该fd从epoll监听树上摘下 -----> 向线程池模型的任务队列中添加任务 ----> 线程池中的某线程被唤醒,成功从任务队列中领取该任务 ----> 线程读取该fd接收缓冲区的数据并根据业务逻辑做处理 ----> 再次调用epoll_ctl()重新将该fd挂载epoll监听树上,并用宏EPOLLIN监听该fd上的读事件
如此一来,在某个线程正在读取某fd上的数据时,多路IO就不再监听该fd,既然不再监听就不会返回,不会返回就不会有别的线程来争夺该fd缓冲区数据;
从宏观上来说就是,对于某个fd,同一时刻下任务队列中只能有一个任务包含了该fd,这样也就保证了同一时刻只有一个线程在操作该fd

以上是关于浅记线程池模型中多个线程对同个fd接收缓冲区读取争夺的方案的主要内容,如果未能解决你的问题,请参考以下文章

java-等待唤醒机制(线程中的通信)-线程池

如何在处理多个文件时最大化吞吐量

在线程争用下等待的最快方法

OpenTSDB原理系列-线程模型

rpc-server端IO服务模型实现:epoll线程池

Android 一个异步SocketHelper