单线程性能秒杀多线程!多路复用IO实现高性能网络服务

Kqueue和其他的多路复用IO的核心是,单消费者同时监听不同种类的生产者,从而提供高性能的单线程IO,减少调度开销 。而Kqueue通过在内核态维持状态提供了更高的性能 。

生产者消费者模型
单线程性能秒杀多线程!多路复用IO实现高性能网络服务

文章插图
 
单Producer和单Consumer
生产者/消费者模型是常见的通信模型,通过共享内核缓冲区环形队列,实现异步的事件通知 。双方只关注缓冲区内的数据,而不关注彼此,因此常常被用于网络通信 。
信号量
为了避免消费者在缓存区未满时无意义的轮询,消费者block直到生产者通知 。wait时线程设置信号量并且block,notify时内核通知所有等待信号的线程状态改为RUNNABLE 。
事实上就是linux的pthread_cond_wait和phread_cond_signal原语 。consumer之所以要带锁wait,是因为在内部进行调度yield_wait前要放掉锁,否则其他线程无法进入临界区;唤醒之后重新获得锁 。(这里指的锁是外部事务的锁)
wait和notify需要增加锁,防止notify先于wait进行 。(这里的锁指的是内部事务的锁)
wait调用的yield_wait在调度时需要临时释放并随后获取内部事务锁,否则会阻塞其他的notify造成全员block 。
send(bb, msg):acquire(bb.lock)while True:if bb.in - bb.out < N:bb.buf[bb.in mod N] <- msgbb.in <- bb.in + 1release(bb.lock)notify(bb.not_empty)returnwait(bb.not_full, bb.block) receive(bb):acquire(bb.lock)while True:if bb.in > bb.out:msg <- bb.buf[bb.out mod N]bb.out <- bb.out + 1release(bb.lock)wait(bb.not_full)returnwait(bb.not_empty, bb.block)Eventcount & Sequencer
这是1979年提出的算法,作为信号量的可替换实现 。Sequencer的目的是处理多producer 。
semaphores
send(Buffer& buffer,Message msg) {t=TICKET(T);AWAIT(buffer.in, t);AWAIT(buffer.out, READ(buffer.in)-N);buffer[READ(buffer.in)%N]=msg;ADVANCE(in);}receive(Buffer& buffer) { AWAIT(buffer.in, READ(buffer.out)); msg = buffer[READ(buffer.out)%N]; ADVANCE(buffer.out); return msg;}
  • AWAIT(event*,val) - 比较event.count和val,如果大于则返回,否则存入线程TCB并yield
  • ADVANCE(event*) - 自增event.count并将所有同event且event.count>val的线程唤醒
  • TICKET(sequencer*) - 原子性自增序号,目的是处理并发的sender
  • READ(event*) - 原子性读event.count,因为可能读操作涉及多memory cell
send等待in超过ticket,相当于拿排队锁轮到自己 。然后等待缓存区未满时写入数据 。
【单线程性能秒杀多线程!多路复用IO实现高性能网络服务】receive等待缓冲区存在数据时读取数据 。


    推荐阅读