关于Netty中的channel.eventLoop.taskQueue正确用法
近日,看netty源码时发现一个问题,关于现在百度上大部分关于NioEventLoop(实际是SingleThreadEventExecutor)中的taskQueue的用法不是很正确,大部分博客都说taskQueue适合用来执行一些比较耗时的task,其实这并不正确,这是会阻塞NIO中的事件的
下面这部分代码来自netty4.1.x源码(NioEventLoop中的run()方法)
if (ioRatio == 100) {
try {
if (strategy > 0) {
processSelectedKeys();//处理NIO事件
}
} finally {
// Ensure we always run tasks.
ranTasks = runAllTasks();//处理taskQueue或者scheduleTaskQueue中的task
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.//处理NIO事件
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);//处理taskQueue或者scheduleTaskQueue中的task
}
} else {
ranTasks = runAllTasks(0); // //处理taskQueue或者scheduleTaskQueue中的task
}
你会发现,当taskQueue中的任务阻塞,其对应的EventLoop就会阻塞,导致该EventLoop不能处理NIO事件,我们的worker可能会处理几百个socketChannel,要是把阻塞任务放在bossGroup的EventLoop的taskQueue中,更是灾难性的。
实例
ServerHandler:
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.executor().execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ByteBuf buf = (ByteBuf)msg;
System.out.println(buf.toString(CharsetUtil.UTF_8) + " " + System.currentTimeMillis());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, I'm server".getBytes(CharsetUtil.UTF_8)));
}
clientHandler
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
String messsage = "hello, I'm client";
ByteBuf buf = Unpooled.directBuffer(256);
buf.writeBytes(messsage.getBytes());
for (int i = 0; i < 50; i++){
ReferenceCountUtil.retain(buf);
ctx.writeAndFlush(buf);
}
}
结果
hello, I’m client 1607617679658
hello, I’m client*49 1607617689658
你会发现俩条结果之间刚好相差了task sleep的时间,所以得出结论,taskQueue是会阻塞我们的EventLoop线程,导致我们不能即时处理NIO事件,所以,不应该在taskQueue中放置耗时太长的任务