[转]网络服务器中生产者/消费者模型中的队列问题
感谢大熊同学为我解惑,这篇帖子的原创权属于大熊。
多线程模型的网络服务器中,一般有专门的网络IO线程,将请求放到请求队列中,此为生产者。然后多个工作线程从队列中获取其中一个请求,进行处理,此是消费者。
通常,通讯使用的队列为锁无关队列。且,为了避免CPU耗慢,当工作线程发现队列为空的时候,要睡眠一会儿。
要命的问题就出在这个睡眠上:
1、假设睡眠的时间是10ms,则当队列为空时,所有工作线程都陆续进入睡眠状态;
2、假设工作线程睡眠期间来了请求,则可能队列中的所有请求都会被延迟10ms才能处理到;
3、在整个服务器的运行周期里,队列为空的几率非常大,因此导致工作线程睡眠的几率也非常大。
毫无疑问,用以上的方法写服务器,延迟高,且性能上不去。
为什么不一有数据到队列,就有工作线程立即去处理呢?(且CPU不能因为轮询空跑)
传统的方法当然是semaphone, condition variable,大熊同学还提了一种开源代码中广泛采用的方案:
1. 用socketpair()系统调用产生两个fd,一个read_fd,一个write_fd
2. 生产者线程写数据到队列后,往write_fd中写入一个字节
3. 工作线程使用read_fd,然后使用epoll_wait等待read_fd上的事件
4. 当epoll_wait返回的时候,工作线程从队列中就能立即取到请求了
5. 工作线程最后还得从read_fd中读出一个字节
以上方案有什么好处呢?
1、队列的检查,不再是基于轮询-睡眠模式,而是基于事件的模式,有数据马上处理,延迟最小;
2、当多个工作线程存在的时候,每个线程都有监听自己的read_fd,所以可以实现按权重等各种复杂的调度算法;
缺点也还是有的:
1、每个工作线程创建的时候,都得调用socketpair()
2、每个线程要占用两个文件句柄
如果可以,后续希望可以测试一下:轮询-睡眠,socketpair()+epoll_wait(),semaphone,condition variable四种方式的性能的优劣。