title | date | categories | tags | description | ||||
---|---|---|---|---|---|---|---|---|
17.6.inEventLoop说明 |
2020-03-04 |
|
|
|
-
一个EventLoopGroup 事件循环组,里面包含一个或者多个EventLoop
-
一个EventLoop在它的整个生命周期中, 只会于一个Thread 进行绑定。
这一点在SingleThreadEventExecutor 中可以看出来。这里面维护了一个Thread。 这个Thread 就是用于执行事件循环的线程, 单线程的奥。
-
所有由EventLoop处理的各种IO事件,都是在这个EventLoop里面的那个Thread 线程上执行的。包括我们各种handler里的方法,其实都在在这个线程里执行的。说它是IO线程? 除了读写,连接注册啥的, 也都是在这个线程里面做的。
-
一个Channel , 在它的整个生命周期当中, 只能注册到一个EventLoop上, 更准确的说, 是指能注册到一个EventLoop里面的Seletor 上。一个EventLoop又只能绑定到一个Thread 上,因此,一个Channel 的诸多Handler里面的方法(更准确的说是与channel关联的pipeline 里面的各种handler), 只会由同一个线程去执行, 不会产生多线程问题。因此, 在handler 的回调里面是不用考虑并发的。都是单线程执行
-
一个EventLoop在运行过程中,会被分配给很多的channel。说明一下:比如我们的workerGroup, 里面有8个 EventLoop。 就是这8个EventLoop要处理成百上千的 连接(Channel)。根据之前说的 round-robin, 第一个channel 来了, 丢给第一个EventLoop, 第二个channel 来了, 就给第二个EventLoop。。。第八个Channel 分给第8个EventLoop处理。第九个channel 来了, 又循环分给第一个EventLoop去处理了。
然而, 一个channel , 在它的生命周期当中, 只能注册到唯一一个 EventLoop 上, 且不能再改变。典型一对多的关系。
-
因此是单线程的,所以不能在handler 里面执行耗时操作,因为这一个线程要服务很多的channel的。一个耗时了,其他所有的channel都要等着了。性能下降。
-
如果想要做耗时操作, 有两种方式: ①在handler 里面使用自己定义的业务线程池来处理 ② 把handler add 到pipeline的时候, 使用addLast的重载方法:addLast(group ...)这样。指定一个EventExectorGroup,这样真正的执行就不在IO线程执行, 而是在单独指定的这个线程组上执行。
* Inserts {@link ChannelHandler}s at the last position of this pipeline. * * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s * methods. * @param handlers the handlers to insert last * */ ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);
这里, add 哪个handler, 哪个handler 的执行就在这个group上, 并不是全部奥。我觉得是这样。
如果add 一个handler, 没指定group, 那默认应该还是在io线程执行的。
如果add handler 指定了group, 那这个handler 里的回调方法就都是在参数中的group上执行。
-
属于同一个channel 的操作, 都能保证他执行的顺序与提交的顺序是相同的。
-
在Netty 中, channel的实现是线程安全的。因此, 我们可以存储一个channel的引用 , 在需要向远程端点发送数据时,可以通过这个channel 相应的方法, 比如write, 直接发送数据。即便可能有多个线程来访问, 来调用, 也不会出现多线程问题。Netty底层会保证消息按照顺序发出。因为不在channel的EventLoop线程的会, 会提交到它那个任务队列, 最终都是由EventLoop的那个线程去执行的。
问题:::
- 顺序问题:::比如channel此时正要执行一个方法,这与任务队列里任务的执行会不会冲突。 比如,正在执行任务队列里的1,耗时10秒,在第5秒的时候,又添加的一个任务2到任务队列。 在10秒钟的时候,我们直接代码让channel执行一个事情3(同一线程的),那此时,2和3谁先执行???
- 如果客户端请求服务器,服务器进行耗时操作(查询数据库),完了之后把数据返回给客户端,这个怎么做? 对于springboot这样的,接口方法返回什么,前端就拿到什么。因此需要同步处理 对于netty的channel read0,可以开线程去处理,处理完了直接用ctx. write andflush就好了,因为全双工socket通信。
- 那问题又来了,如果是netty实现的http服务器呢?在channel read 0里面,又要返回,又不能执行耗时操作?
我知道了, 大概是这样:
Http 是基于请求响应模式的,客户端向服务器发送一个Http请求,然后服务器将返回一个Http响应。
对于 SpringBoot 或者 Serverlet 这样的,在接口里面返回什么,就直接进行Http响应了。所以要同步处理。如果开个线程去处理的话, 接口方法一返回, 底层就去进行Http响应了,这样一次Http请求就完了。等线程处理完了, 也没用了。
对于Netty 这样的实现的Http服务器,请求来了,在 channelRead方法里,返回了, 并没有进行HTTP响应。开个线程,执行操作,操作执行完了, 再write,这时候才执行HTTP响应,这个时候, 一次完整的HTTP请求才完成。
inEventLoop说明:
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
eventLoop.inEventLoop() 这个判断,就是为了防止产生多线程的问题。
如果是, 就代表: 当前正在执行注册操作的线程, 就是eventLoop里面的那个线程, 那么就放心的交给他来做。
如果不是,就嗲表:正在执行注册流程操作的这个线程, 不是EventLoop里面的那个线程。如果直接执行, 就会产生多线程问题。 那么就提交一个任务, 提到eventloop上, 让eventloop的那个thread 再去执行,还是单线程执行, 这样避免多线程的问题。
Netty精髓