运动鞋网站的建设风格seo国外英文论坛
前言
简单地说,线程模型指定了操作系统、编程语言、框架或者应用程序的上下文中的线程管理的关键方面。显而易见地,如何以及何时创建线程将对应用程序代码的执行产生显著的影响,因此开发人员需要理解与不同模型相关的权衡。无论是他们自己选择模型,还是通过采用某种编程语言或者框架隐式地获得它,这都是真实的。在这次,我们将会学习线程模型的概念。它强大但又易用,并且和Netty 的一贯宗旨一样,旨在简化你的应用程序代码,同时最大限度地提高性能和可维护性。我们还将讨论致使选择当前线程模型的经验。
幸运的是,我之前读过JAVA并发编程艺术这本书,所以我认为这一章对我的提升应该比较大
PS(个人理解,仅供参考,如有不对,权当笑话):谈到并发编程,很多时候大家都会自然想到高并发,但是对我而言,所谓并发编程,其实就是将线程合理利用,我每天上班都会经过很长的高速公路,其中有一段路特别的拥堵,但是他却有四条车道,其他畅通的道路,可能只有两条车道,有一天,我追根溯源,发现堵车的源头是因为四条车道有一条在施工,我很好奇,四条路一条在施工,应该有三条,那为什么两条的反而不堵呢,我进行了统计和特意的观察,我发现,四条车道的车流量极大,这就是所谓的并发量很大,4条线程在工作,有一条堵了,资源利用低,造成了卡顿。但是两条路车流相对较好,也有好的设计,所以都能利用。这就是高并发的情况下,最大效率的利用资源会使得我们的程序表现良好,如何利用资源更好,显然在很大的并发量的时候,有时候单线程不能很好的处理,所以就有了并发编程的概念,当然并发编程并不仅仅运用于高并发场景,它可以在一些工具层有很好的表现,例如读取百万数据并同步等场景。所以说高并发指的是系统并发量大,我们如何去承载这些并发,最大限度的去将单机的吞吐量达到最大,而并发编程是如何提高程序效率。对我而言,基本概念是不同的。(可能随着阅历的增长,觉得这个观点可笑,留作日后批评)。
线程模型概述
我们将介绍常见的线程模型,随后将继续讨论Netty 过去以及当前的线程模型,并评审它们各自的优点以及局限性。,线程模型确定了代码的执行方式。由于我们总是必须规避并发执行可能会带来的副作用,所以理解所采用的并发模型(也有单线程的线程模型)的影响很重要。忽略这些问题,仅寄希望于最好的情况(不会引发并发问题)无疑是赌博——赔率必然会击败你。
因为具有多核心或多个CPU 的计算机现在已经司空见惯,大多数的现代应用程序都利用了复杂的多线程处理技术以有效地利用系统资源。相比之下,在早期的Java 语言中,我们使用多线程处理的主要方式无非是按需创建和启动新的Thread 来执行并发的任务单元——一种在高负载下工作得很差的原始方式。Java 5 随后引入了Executor API,其线程池通过缓存和重用Thread 极大地提高了性能。
基本的线程池化模式可以描述为:
从池的空闲线程列表中选择一个Thread,并且指派它去运行一个已提交的任务(一个Runnable 的实现)
当任务完成时,将该Thread 返回给该列表,使其可被重用。
虽然池化和重用线程相对于简单地为每个任务都创建和销毁线程是一种进步,但是它并不能消除由上下文切换所带来的开销,其将随着线程数量的增加很快变得明显,并且在高负载下愈演愈烈。此外,仅仅由于应用程序的整体复杂性或者并发需求,在项目的生命周期内也可能会出现其他和线程相关的问题。
EventLoop 接口
运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能。与之相应的编程上的构造通常被称为事件循环—一个Netty 使用了interface io.netty.channel.EventLoop 来适配的术语。
看一下这个接口吧:
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {@OverrideEventLoopGroup parent();
}
继承了两个接口,里面就一个parent方法
private final EventExecutorGroup parent;private final Collection<EventExecutor> selfCollection = Collections.<EventExecutor>singleton(this);protected AbstractEventExecutor() {this(null);}protected AbstractEventExecutor(EventExecutorGroup parent) {this.parent = parent;}@Overridepublic EventExecutorGroup parent() {return parent;}
看样子是返回了一个EventExecutorGroup。这个里面具体怎么工作的,这个EventLoop又是什么?
@Overrideprotected void run() {for (;;) {Runnable task = takeTask();if (task != null) {task.run();updateLastExecutionTime();}if (confirmShutdown()) {break;}}}
通过这个我看到了,其实就是一个阻塞队列,然后自旋操作,什么时候有东西push进去了,取出来,运行这个任务,修改时间。很简单呢的小玩意儿。
Netty 的EventLoop 是协同设计的一部分,它采用了两个基本的API:并发和网络编程。io.netty.util.concurrent是不是很像juc。这个包里面提供了很多执行器的接口和类,其次io.netty.channe为了l与Channel进行交互,扩展了这些类。一个EventLoop 将由一个永远都不会改变的Thread 驱动,同时任务(Runnable 或者Callable)可以直接提交给EventLoop 实现,以立即执行或者调度执行。根据配置和可用核心的不同,可能会创建多个EventLoop 实例用以优化资源的使用,并且单个EventLoop 可能会被指派用于服务多个Channel。
Netty 4 中的I/O 和事件处理
由I/O 操作触发的事件将流经安装了一个或者多个ChannelHandler 的ChannelPipeline。传播这些事件的方法调用可以随后被ChannelHandler 所拦截,并且可以按需地处理事件。
事件的性质通常决定了它将被如何处理;它可能将数据从网络栈中传递到你的应用程序中,或者进行逆向操作,或者执行一些截然不同的操作。但是事件的处理逻辑必须足够的通用和灵活,以处理所有可能的用例。因此,在Netty 4 中,所有的I/O操作和事件都由已经被分配给了EventLoop的那个Thread来处理。
Netty 3 中的I/O 操作
在以前的版本中所使用的线程模型只保证了入站(之前称为上游)事件会在所谓的I/O 线程(对应于Netty 4 中的EventLoop)中执行。所有的出站(下游)事件都由调用线程处理,其可能是I/O 线程也可能是别的线程。开始看起来这似乎是个好主意,但是已经被发现是有问题的,因为需要在ChannelHandler 中对出站事件进行仔细的同步。简而言之,不可能保证多个线程不会在同一时刻尝试访问出站事件。例如,如果你通过在不同的线程中调用Channel.write()方法,针对同一个Channel 同时触发出站的事件,就会发生这种情况。当出站事件触发了入站事件时,将会导致另一个负面影响。当Channel.write()方法导致异常时,需要生成并触发一个exceptionCaught 事件。但是在Netty 3 的模型中,由于这是一个入站事件,需要在调用线程中执行代码,然后将事件移交给I/O 线程去执行,然而这将带来额外的上下文切换。Netty 4 中所采用的线程模型,通过在同一个线程中处理某个给定的EventLoop 中所产生的所有事件,解决了这个问题。这提供了一个更加简单的执行体系架构,并且消除了在多个ChannelHandler 中进行同步的需要(除了任何可能需要在多个Channel中共享的)。
任务调度
偶尔,你将需要调度一个任务以便稍后(延迟)执行或者周期性地执行。例如,你可能想要注册一个在客户端已经连接了5 分钟之后触发的任务。一个常见的用例是,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该Channel 了。
JDK 的任务调度API
在Java 5 之前,任务调度是建立在java.util.Timer 类之上的,其使用了一个后台Thread,并且具有与标准线程相同的限制。随后,JDK 提供了java.util.concurrent 包,它定义了interface ScheduledExecutorService。
使用EventLoop 调度任务
ScheduledExecutorService 的实现具有局限性,例如,事实上作为线程池管理的一部分,将会有额外的线程创建。如果有大量任务被紧凑地调度,那么这将成为一个瓶颈。Netty 通过Channel 的EventLoop 实现任务调度解决了这一问题。
public static void scheduleViaEventLoop() {Channel ch = new NioSocketChannel(); ScheduledFuture<?> future = ch.eventLoop().schedule(new Runnable() {@Overridepublic void run() {System.out.println("60 seconds later");}}, 60, TimeUnit.SECONDS);}
经过60 秒之后,Runnable 实例将由分配给Channel 的EventLoop 执行。如果要调度任务以每隔60 秒执行一次,请使用scheduleAtFixedRate()方法。
浅看一下源码。觉得自己又行了!
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {if (inEventLoop()) {scheduleFromEventLoop(task);} else {final long deadlineNanos = task.deadlineNanos();// task will add itself to scheduled task queue when run if not expiredif (beforeScheduledTaskSubmitted(deadlineNanos)) {execute(task);} else {lazyExecute(task);// Second hook after scheduling to facilitate race-avoidanceif (afterScheduledTaskSubmitted(deadlineNanos)) {execute(WAKEUP_TASK);}}}return task;}
不解释了 比较简单。但是有必要学习一下执行细节。
执行细节
线程管理
Netty线程卓越的能力来自于堆当前线程身份的判断,也就是说,确定是否是分配给当前Channel以及它的EventLoop的那一个线程。因为EventLoop将处理一个Channle所有的生命周期的事件。
如果(当前)调用线程正是支撑EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。这也就解释了任何的Thread 是如何与Channel 直接交互而无需在ChannelHandler 中进行额外同步的。注意,每个EventLoop 都有它自已的任务队列,独立于任何其他的EventLoop。
··
注意注意:“永远不要将一个长时间运行的任务放入到执行队列中,因为它将阻塞需要在同一线程上执行的任何其他任务,”如果必须要进行阻塞调用或者执行长时间运行的任务,我们建议使用一个专门的EventExecutor。
除了这种受限的场景,如同传输所采用的不同的事件处理实现一样,所使用的线程模型也可以强烈地影响到排队的任务对整体系统性能的影响
EventLoop/线程的分配
服务于Channel 的I/O 和事件的EventLoop 包含在EventLoopGroup 中。根据不同的传输实现,EventLoop 的创建和分配方式也不同。
异步传输
异步传输实现只使用了少量的EventLoop(以及和它们相关联的Thread),而且在当前的线程模型中,它们可能会被多个Channel 所共享。这使得可以通过尽可能少量的Thread 来支撑大量的Channel,而不是每个Channel 分配一个Thread。
例如:有三个EventLoopGroup,每一个组中都会有几个EventLoop,然后有新的Channel进来,就给他分配一个EventLoop。每一个EventLoop都有一个线程关联,在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的EventLoop可能会被分配给多个Channel。一旦一个Channel 被分配给一个EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的Thread)。请牢记这一点,因为它可以使你从担忧你的ChannelHandler 实现中的线程安全和同步问题中解脱出来。
另外,需要注意的是,EventLoop 的分配方式对ThreadLocal 的使用的影响。因为一个EventLoop 通常会被用于支撑多个Channel,所以对于所有相关联的Channel 来说,ThreadLocal 都将是一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上下文中,它仍然可以被用于在多个Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件。
阻塞传输
得到的保证是每个Channel 的I/O 事件都将只会被一个Thread(用于支撑该Channel 的EventLoop 的那个Thread)处理。
结束语
学习了Netty的线程模型和知道了EventLoop的原理,后面会专门在并发编程专栏中重点涉及一下线程模型,和工作中的应用。