研究了一段时间Nio框架,有Netty和Tomcat的Nio Connector总结了一些共性的问题的解决方案。
1. Selector的register和select有锁冲突,例如tomcat的Nio Connector,他采用的Acceptor和Poller的模式,Acceptor只负责接收socket,Poller是负责读写的IO线程,这种模式不用于nginx,Poller(worker)既负责接收和读写的抢占式模块。这样Acceptor和Poller存在的一个交互就是register,Acceptor需要把接收到的socket注册给Poller。Poller实现了Selector执行select和并且遍历selectionKey操作的封装。
假设Poller正在执行Selector的select方法并且同时Acceptor又接收了新的socket并执行register操作,这时候就会发生锁等待,集体参看http://xiaoz5919.iteye.com/blog/1518473。该如何解决这个问题呢。
Tomcat是这样解决的,把注册当成一个event,当有注册发生时添加一个注册事件到队列中,Poller在每次执行select之前先处理注册事件队列。这样保证了register和select的执行顺序是永远是一致的,先执行register再执行select,有效地避免了锁冲突。
再来看看代码,
boolean hasEvents = events();//处理register和cancel事件
// Time to terminate?
if (close) {
timeout(0, false);
break;
}
try {
if ( !close ) {
if (wakeupCounter.getAndSet(-1) > 0) {
//if we are here, means we have other stuff to do
//do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
再看一个细节问题,selector的wakeup是一个很昂贵的工作,假如有多个socket来register也只需一次wakeup,所以tomcat设置了wakeupCounter变量来操作唤醒次数并且只有wakeup==0时才执行一次唤醒操作。