3 深入理解Netty, 线程篇2, 深入分析netty的线程模型

        上篇讲了reactor的线程模型,我们说netty 是属于主从线程模型,下面我们一起来看看netty的线程模型。

        本片文章分为两部分:

        第一部分是分析 netty的线程模型,这部分我只会贴关键源码,我觉得贴太多源代码会很难理解,所以我一般会用贴关键源代码和伪代码表示。

        第二部分是分析几道关于这部分的面试题。

本次分析用的netty版本是    4.1.65.Final

第一部分:

        我们先从一个服务端demo开始:

   public static void main(String[] args) throws Exception {
        EventLoopGroup boosGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(3);
        try{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boosGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
             ChannelFuture cf = bootstrap.bind(6668).sync();
             cf.channel().closeFuture().sync();
        }finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

 总览

        大家先看看这幅图:

 先总结一下这幅图:

 1  netty 是用了netty 主从模式, boosGroup为主线程池,用于接收ACCEPT事件,workerGroup用于处理耗时的IO事件和普通任务。

2 NioEventLoopGroup 和 EventLoop 是池和线程的关系,一个NioEventLoopGroup 里面有一个或多个EventLoop

下面我们分三部分来给分别说一下:

第一,NioEventLoopGroup 和 EventLoop 具体是做什么的

第二,boosGroup是如何被启动的

第三,boosGroup 是 如何驱动  workerGroup

        这样大家就会对netty大的框架有一个认识,EventLoop 作为 reactor 线程是做什么的,

boosGroup 的  reactor  是怎样被启动的, workerGroup 的reactor 线程 是怎样被启动的。

第一   我们一起来看看  NioEventLoopGroup 的构造函数 做了什么:

        第一步  new  NioEventLoopGroup 时,调用父类(SingleThreadEventExecutor)的构造函数,初始化话了若干个  EventLoop

        问题:多少个EventLoop

        答:根据传进去的参数决定,如果不传默认 cup核心数*2

        核心代码如下:

         第一调用  NioEventLoopGroup  的父类构造函数MultithreadEventExecutorGroup,

         此处 做了两件事:

         1 初始化的线程池

         2 初始化了EventLoop数组

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        children = new EventExecutor[nThreads];
        for (int i = 0; i < nThreads; i ++) {
        children[i] = newChild(executor, args);
        }

    }

  

 第二 初始化 EventLoop的时候,创建了 Selector,供后面监听事件

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory queueFactory) {
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
                rejectedExecutionHandler);
        this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
        final SelectorTuple selectorTuple = openSelector();
        this.selector = selectorTuple.selector;
        this.unwrappedSelector = selectorTuple.unwrappedSelector;
    }

 第三调用 EventLoop 的父类构造函数,初始化工作入口启动类

  protected SingleThreadEventExecutor() {
        // 这里 eventLoop工作的启动入口
        this.executor = ThreadExecutorMap.apply(executor, this);  
    }

  public static Executor apply(final Executor executor, final EventExecutor eventExecutor) {
        return new Executor() {
            @Override
            public void execute(final Runnable command) {
                executor.execute(apply(command, eventExecutor));
            }
        };
    }

我们用伪代码来看看 EventLoop是如何工作的:

EventLoop{
    Exector exector;
    Selector selecor;

    for (;;) {
	 // 判断队列是否有任务,有则执行 selectNow() , 没有则返回  -1
	 strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
	 // 返回 -1 则说明队列没有任务
	 if(strategy==-1){
	    strategy = select();
	 } 
	 
	 if(strategy>0){
	    processSelectedKeys();
		ranTasks = runAllTasks();
	 }
	 }

}

第一:先判断 是否有本地任务需要执行

第二:如果有则调用selectNow 立即返回就绪事件,目的是防止延误非IO任务的执行

第三:如果没有本地任务,则调用select 监听IO事件

总结一下就是:

第一: NioEventLoopGroup主要完成对 EventLoop的初始化,但是还没有正式启动EventLoop。只是定义了一下,只有正在有任务的时候才会启动。 

第二:NioEventLoopGroup 和 EventLoop 是池和线程的关系

第三:因为EventLoop是用于监听并处理事件的,所以EventLoop肯定持有一个Selector

第二是 我们来看看 boosGroup 的线程是如何被启动

        我们先说结论,boosGroup是在 bootstrap.bind(6668) 被启动,bootstrap.bind 做主要做了初始化ServerSocket 并 注册到 bossGroup,注册的时候 启动了boosGroup 线程。

        我们来直接看关键代码

        我们先来大概的代码调用链路:

    bootstrap.bind(6668) ----> AbstractBootstrap.dobind()---->AbstractBootstrap. initAndRegister()----->config().group().register(channel)---->next().register(channel)----->promise.channel().unsafe().register(this, promise)---->AbstractChannel.register------> eventLoop.execute(new Runnable() {
    @Override
    public void run() {
        register0(promise);
    }
});

        现在我们来看看,eventLoop.execute 做了什么事情:

  1 将任务加入队列,后面线程会消费

   private void execute(Runnable task, boolean immediate) {
         addTask(task);
         startThread();
    }
    private void startThread() {
       doStartThread();
    }

    private void doStartThread() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
               SingleThreadEventExecutor.this.run();
            }
        });
    }

  2 启动工作线程 监听IO事件

    @Override
    protected void run() {
        for (;;) {
        processSelectedKeys();
        ranTasks = runAllTasks();
        }
    }

   总结一下就是,bootstrap.bind 的时候,注册 fd 时启动bootstrap 的 eventLoop

第三是 boosGroup 是 如何驱动  workerGroup

        前面因为已经将ServerSocket 注册到 boosGroup线程,所以boosGroup线程 是能监听到 accept事件的

        boosGroup 驱动 workerGroup,是boosGroup线程接收到 accept 事件后,注册到 workerGroup 线程后启动的。

        我们现在来看看关键源代码:

        NioEventLoop.run---->NioEventLoop.processSelectedKey() --->NioEventLoop.unsafe.read()---->pipeline.fireChannelRead(readBuf.get(i))---->ServerBootstrap.channelRead()---->childGroup.register(child)  ----> 后面就是走第二 的注册流程启动的

ServerBootstrap.channelRead()

 public void channelRead(ChannelHandlerContext ctx, Object msg) {
             childGroup.register(child)
 }

        这里先说明几个问题:

        第一:NioEventLoop.run 的NioEventLoop 是 boosGroup 的EventLoop,接收事件后注册到workerGroup.

        第二:接收到accept 事件后,传递fireChanelRead 到pipeline。pipeline 大家pipeline可以简单理解成保存channelHandler的双向链表,用于处理具体的任务的。

        第三:因为 ServerBootstrap 也是一个handler,也是注册到 pipeline里面的,所以会调用到

第二部分:

问题一:  netty 中 channel 和 jdk nio 包下的channel 是什么关系呢

答:netty 是基于 java nio 上实现,所以核心功能还是会依赖于 java jdk的核心组件,Netty 的 channel 是 封装了 java jdk 上的channel。Channel 接口的定义尽量大而全,为socketChannel 和 ServerSocketChannel 提供同一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。

问题二:channel 注册到 nioEventLoop 的过程,详细的说下

答:nioEventLoop 可以说是 netty 的 reactor 线程,它除了处理I/O事件,还能处理普通的task。既然能处理 I/O事件,所以nioEventLoop 内部肯定封装了 java jdk 的 selector 对象,用于监听事件。所以channel 注册到 nioEventLoop 就是做了一件事,将netty channel对象内部维护的 jdk nio channel 注册到这个 NioEventLoop 的 selector 对象内

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值