Why do we really need multiple netty boss threads?
Asked Answered
M

2

12

I'm really confused about the number of threads for a boss group. I can't figure out a scenario where we need more than one boss thread. In do we need more than a single thread for boss group? the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps, but I don't see the reason for it.

Consider this simple Echo server:

public class EchoServer {

private final int port;
private List<ChannelFuture> channelFutures = new ArrayList<ChannelFuture>(2);

public EchoServer(int port) {
    this.port = port;
}

public void start() throws Exception {

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup(4);

    for (int i = 0; i != 2; ++i) {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class) // the channel type
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch)
                            throws Exception {
                        System.out.println("Connection accepted by server");
                        ch.pipeline().addLast(
                                new EchoServerHandler());
                    }
                });

        // wait till binding to port completes
        ChannelFuture f = b.bind(port + i).sync();
        channelFutures.add(f);
        System.out.println("Echo server started and listen on " + f.channel().localAddress());
    }

    for (ChannelFuture f : channelFutures)
        f.channel().closeFuture().sync();

    // close gracefully
    workerGroup.shutdownGracefully().sync();
    bossGroup.shutdownGracefully().sync();
}

public static void main(String[] args) throws Exception {
    if (args.length != 1) {
        System.err.println(
                "Usage: " + EchoServer.class.getSimpleName() +
                        " <port>");
        return;
    }
    int port = Integer.parseInt(args[0]);
    new EchoServer(port).start();
}

In the above example, I create a bossGroup with 1 thread and workerGroup with 4 threads and share both event groups to two different bootstraps that bind to two different ports (e.g. 9000 and 9001). Below is my handler:

@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelRead(ChannelHandlerContext ctx,
                        Object msg)  throws Exception  {
    ByteBuf in = (ByteBuf) msg;
    System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8) + " from channel " + ctx.channel().hashCode());
    ctx.write(in);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
    System.out.println("Read complete for channel " + ctx.channel().hashCode());
    // keep channel busy forever
    while(true); 
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx,
                            Throwable cause) {
    cause.printStackTrace();
    ctx.close();
}
}

In my handler above, I purposely keep the channel busy by doing while(true); Now if I start my app with parameter 9000, it will create two server bootstraps that bind at port 9000 and 9001.

Echo server started and listen on /0:0:0:0:0:0:0:0:9090
Echo server started and listen on /0:0:0:0:0:0:0:0:9091

Now, if I connect to both ports and start sending data, the maximum # of connections that can be received is 4, which makes sense since I have created 4 worker threads and keep their channel busy without closing it:

echo 'abc' > /dev/tcp/localhost/9000
echo 'def' > /dev/tcp/localhost/9000
echo 'ghi' > /dev/tcp/localhost/9001
echo 'jkl' > /dev/tcp/localhost/9000
echo 'mno' > /dev/tcp/localhost/9001 # will not get connected

You can also do:

telnet localhost 9000 -> then send data "abc"
telnet localhost 9000 -> then send data "def"
telnet localhost 9001 -> then send data "ghi"
telnet localhost 9000 -> then send data "jkl"
telnet localhost 9001 -> # will not get connected

what I don't understand is, I have one boss thread and I'm able to connect to two ports with two server bootstraps. So why do we need more than one boss thread (and by default, the # of boss threads is 2*num_logical_processors)?

Thanks,

Mylor answered 14/12, 2015 at 19:32 Comment(0)
T
4

the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps, but I don't see the reason for it.

As Norman Maurer said, it's not necessary, but it's very useful.

If you are using 1 thread for 2 different bootstraps, it means that you can't handle connections to this bootstraps simultaneously. So in very very bad case, when boss thread handle only connections for one bootstrap, connections to another will never be handled.

Same for workers EventLoopGroup.

Turbo answered 14/12, 2015 at 23:42 Comment(3)
So one boss thread can handle multiple connections, but not simultaneously? And, it is useful to have multiple boss threads (e.g. in a web server where we expect multiple simultaneous connections?) Also, is my understanding or worker threads correct? Thanks.Mylor
@Mylor boss threads handle connections and pass processing to worker threads. Yeh, it's useful to have multiple boss threads. But no need to much, 'cause in general worker thread works more time than boss thread.Turbo
Why have 2x threads per core instead of 1x per core? How can one core do two things at once?Jenifferjenilee
R
2

When you bind() a new port, Netty registy a new ServerSocketChannel to the next() eventloop in bossGroup, so i think multiple threads boss may be used on multiple ports applications.

Razor answered 13/12, 2021 at 9:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.