Vert.x的线程的总结1

1、Vert.x线程模型
Vert.x的线程模型主要分成两种:Event Loop线程和Worker线程。
Event Loop用于处理事件,每一个Event Loop斗鱼唯一的线程相绑定,这个线程称作Event Loop线程:其特点:Event Loop线程不能阻塞,否则事件无法处理。
Worker线程:用于执行阻塞任务,这既可以执行阻塞任务,又不阻塞Event Loop线程。

2. Event Loop线程
如果像Node.js一样只有单个Event Loop的话就不能充分利用多核CPU的性能了。为了充分利用多核CPU的性能,**Vert.x中提供了一组Event Loop线程。每个Event Loop线程都可以处理事件。**为了保证线程安全,防止资源争用,Vert.x保证了某一个Handler总是被同一个Event Loop线程执行,这样不仅可以保证线程安全,而且还可以在底层对锁进行优化提升性能。所以,只要开发者遵循Vert.x的线程模型,开发者就不需要再担心线程安全的问题,这是非常方便的。
首先回顾一下Event Loop线程,它会不断地轮询获取事件,并将获取到的事件分发到对应的事件处理器中进行处理:在这里插入图片描述
Vert.x线程模型中最重要的一点就是:永远不要阻塞Event Loop线程。因为一旦处理事件的线程被阻塞了,事件就会一直积压着不能被处理,整个应用也就不能正常工作了。

Vert.x中内置一种用于检测Event Loop是否阻塞的线程:“vertx-blocked-thread-checker”。一旦Event Loop处理某个事件的时间超过一定阈值(默认为2000ms)就会警告,如果阻塞的时间过长就会抛出异常。Block Checker的实现原理比较简单,底层借助了JUC的TimerTask,定时计算每个Event Loop线程的处理事件消耗的时间,如果超时就进行相应的警告。

3. Vert.x Thread

Vert.x中的Event Loop线程及Worker线程都用VertxThread类表示,并通过VertxThreadFactory线程工厂来创建。VertxThreadFactory创建Vert.x线程的过程非常简单:

@Override
public Thread newThread(Runnable runnable){
VertxThread t = newVertxThread(runnable, prefix + threadCount.getAndIncrement(), worker, maxExecTime);
if (checker != null) {
checker.registerThread(t);
}
addToMap(t);
t.setDaemon(false);
return t;
}

除了创建VertxThread线程之外,VertxThreadFactory还会将此线程注册至Block Checker线程中以监视线程的阻塞情况,并且将此线程添加至内部的weakMap中。这个weakMap作用只有一个,就是在注销对应的Verticle的时候可以将每个VertxThread中的Context实例清除(unset)。为了保证资源不被一直占用,这里使用了WeakHashMap来存储每一个VertxThread。当里面的VertxThread的引用不被其他实例持有的时候,它就会被标记为可清除的对象,等待GC。

至于VertxThread,它其实就是在普通线程的基础上存储了额外的数据(如对应的Vert.x Context,最大执行时长,当前执行时间,是否为Worker线程等),这里就不多讲了。

4. Vert.x Context
Vert.x底层中一个重要的概念就是Context,每个Context都会绑定着一个Event Loop线程(而一个Event Loop线程可以对应多个Context)。我们可以把Context看作是控制一系列的Handler的执行作用域及顺序的上下文对象

每当Vert.x底层将事件分发至Handler的时候,Vert.x都会给此Handler钦点一个Context用于处理任务:

  • 如果当前线程是Vert.x线程(VertxThread),那么Vert.x就会复用此线程上绑定的Context;如果没有对应的Context就创建新的

  • 如果当前线程是普通线程,就创建新的Context

Vert.x中存在三种Context,与之前的线程种类相对应:

  1. EventLoopContext

  2. WorkerContext

  3. MultiThreadedWorkerContext

    4.1 Event loop context
    每个Event Loop Context都会对应着唯一的一个EventLoop,即一个Event Loop Context只会在同一个Event Loop线程上执行任务。在创建Context的时候,Vert.x会自动根据轮询策略选择对应的EventLoop:

protected ContextImpl(VertxInternal vertx, WorkerPool internalBlockingPool, WorkerPool workerPool, String deploymentID, JsonObject config,
                        ClassLoader tccl) {
    // ...
    EventLoopGroup group = vertx.getEventLoopGroup();
    if (group != null) {
      this.eventLoop = group.next();
    } else {
      this.eventLoop = null;
    }
    // ...
  }

Event Loop Context会在对应的EventLoop中执行Handler进行事件的处理(IO事件,非阻塞)。Vert.x会保证同一个Handler会一直在同一个Event Loop线程中执行,这样可以简化线程模型,让开发者在写Handler的时候不需要考虑并发的问题,非常方便。

我们来粗略地看一下Handler是如何在EventLoop上执行的。EventLoopContext中实现了executeAsync方法用于包装Handler中事件处理的逻辑并将其提交至对应的EventLoop中进行执行:

public void executeAsync(Handler<Void> task) {
  // No metrics, we are on the event loop.
  nettyEventLoop().execute(wrapTask(null, task, true, null));
}

这里Vert.x使用了wrapTask方法将Handler封装成了一个Runnable用于向EventLoop中提交。代码比较直观,大致就是检查当前线程是否为Vert.x线程,然后记录事件处理开始的时间,给当前的Vert.x线程设置Context,并且调用Handler里面的事件处理方法。具体请参考源码,这里就不贴出来了。

那么把封装好的task提交到EventLoop以后,EventLoop是怎么处理的呢?这就需要更多的Netty相关的知识了。根据Netty的模型,Event Loop线程需要处理IO事件,普通事件(即我们的Handler)以及定时事件(比如Vert.x的setTimer)。Vert.x会提供一个NETTY_IO_RATIO给Netty代表EventLoop处理IO事件时间占用的百分比(默认为50,即IO事件时间占用:非IO事件时间占用=1:1)。当EventLoop启动的时候,它会不断轮询IO时间及其它事件并进行处理:

@Override
protected void run() {
    for (;;) {
        try {
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));

                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
                    // fallthrough
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                processSelectedKeys();
                runAllTasks();
            } else {
                final long ioStartTime = System.nanoTime();

                processSelectedKeys();

                final long ioTime = System.nanoTime() - ioStartTime;
                runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
            }

            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    break;
                }
            }
        } catch (Throwable t) {
            // process the error
            // ...
        }
    }
}

这里面Netty会调用processSelectedKeys方法进行IO事件的处理,并且会计算出处理IO时间所用的事件然后计算出给非IO事件处理分配的时间,然后调用runAllTasks方法执行所有的非IO任务(这里面就有我们的各个Handler)。
runAllTasks会按顺序从内部的任务队列中取出任务(Runnable)然后进行安全执行。而我们刚才调用的NioEventLoop的execute方法其实就是将包装好的Handler置入NioEventLoop内部的任务队列中等待执行。

4.2 Worker context

顾名思义,Worker Context用于跑阻塞任务。与Event Loop Context相似,每一个Handler都只会跑在固定的Worker线程下。
Vert.x还提供一种Multi-threaded worker context可以在多个Worker线程下并发执行任务,这样就会出现并发问题,需要开发者自行解决并发问题。因此一般情况下我们用不到Multi-threaded worker context。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值