【一次bug引发的思考】Selector的selectedKeys方法

最近在写reactor的多线程模型时,遇到了一些意料之外的收获,便将博客记录在这里。

Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
 while (iterator.hasNext()){
	 SelectionKey key = iterator.next();
	 Runnable runnable = (Runnable) key.attachment();
	 if (runnable instanceof Acceptor)
	     runnable.run();
	 else if (runnable instanceof Handler)
	     threadPool.execute(runnable);
	 iterator.remove();
 }

先简单介绍下这段代码,主线程会不停的进行select操作,来捕获IO就绪的各种事件,包括连接操作,包括读操作等等。

如果是连接操作,便会在主线程里完成连接。
如果是读操作,便会在线程池里完成读取操作。

在使用netcat进行连接,然后发送数据给server端时,奇怪的事情发生了。

 threadPool.execute(runnable);

这段代码被重复的执行了好几次(有时候4次,有时候5次),由于是读取操作,因此第一次执行能读取出数据,后面读取的数据均为空。

将这段代码改成如下同步执行的样子,发现没有任何问题。

runnable.run();

思考了好久,终于弄明白了。
reactor模型本质上是基于TCP进行消息传播的,而TCP有个数据缓冲区,也就是说,数据抵达传输层后,会存储在缓冲区,然后应用层来取走数据。因此到netcat发送的数据抵达server端后,selector执行select操作,以此来告知我们IO事件准备就绪。

那么,当我们把读取数据的过程放在子线程时,极有可能导致我们下一次select的时候,前一次读事件里的缓冲区的数据还没有取完,因此还能select出读事件,从而导致执行多次的情况发生。

所以说并不是执行了iterator.remove(),就能保证下一次不select出曾经出现的事件(当然执行terator.remove()是必须的),我们还要确保能及时取走缓冲区里的数据!

猜你喜欢

转载自blog.csdn.net/weixin_38014667/article/details/107820680