美文网首页
Netty中真的没有使用锁吗?

Netty中真的没有使用锁吗?

作者: 书唐瑞 | 来源:发表于2021-04-01 22:41 被阅读0次

    Netty号称是一个事件驱动&异步串行无锁化的网络通信框架.

    图片.png

    在Netty的官方网站中声称, 它是一个异步的, 事件驱动的网络框架.

    关于事件驱动, 在之前的文章中也简单提到过, Netty内部会一直轮询ACCEPT,READ,WRITE,CONNECT等事件, 根据轮询到的不同的事件, 调用不同的方法, 做出不同的响应. 正如我们平时说的, 操作系统是基于中断驱动的, 而Netty是基于事件驱动的.

    关于异步这块, 我给它的准确定义是异步串行无锁化. 然而在Netty内部会包含两类线程, 一类是执行IO操作的IO线程, 比如执行上面说的各种事件的线程就是IO线程. 还有一类线程是非IO线程(这不是废话嘛), 也就是业务线程. 虽然我说它是异步串行无锁化, 但不准确, 因为我没说主语. 到底是IO线程在异步串行无锁化, 还是非IO线程在异步串行无锁化呢? 这里说的是非IO线程, 非IO线程在执行写操作的时候, 会把写操作封装成一个写任务, 然后提交到与IO线程唯一绑定的任务队列里, 由IO线程从队列里面取出任务去执行. 而非IO线程提交完任务之后就返回了, 可以继续向下执行. 而且即便多个非IO线程同时向任务队列中提交任务, 也不会发生阻塞, 也不会加锁, 因为它是通过CAS方式操作队列的. 这个高性能的队列, 自然和JDK自带的无缘, 它是jctools里的一个队列(org.jctools.queues.MpscUnboundedArrayQueue), 专为并发而生的队列. 任务被提交到任务队列之后, IO线程就会从队列中取出任务, 逐个串行执行.

    图片.png

    IO线程一直无限循环地沉浸在轮询IO事件-处理IO事件-执行队列中的任务这三件事情无法自拔.在这个过程中, IO线程也没有使用加锁的逻辑.

    那么在Netty中到底哪里会使用加锁的逻辑呢? 是在申请内存的时候.

    举例来说, 当网卡接收到数据之后, 通过中断通知CPU, CPU响应硬中断, 同时发起软中断请求. 操作系统的ksoftirqd线程执行软中断, 将网络数据通过协议栈处理, 最终放到socket的接收缓冲区, 唤醒IO线程(IO线程可能执行了epoll处于阻塞状态). IO线程就会读取数据, 然而数据肯定需要内存空间来保存. 这个时候IO线程就会申请堆外空间进行存储这些数据. 在申请堆外空间的时候可能就会发生加锁的情况. 关于内存申请和释放这块, 在接下来的文章中就会介绍到. 这里简单描述下IO线程申请内存空间的流程.

    图片.png

    如上图所示, 当IO线程申请内存的时候, 首先会从自身的PoolThreadCache中查找是否有可用的空闲内存, 这个时候是不需要加锁的, 因为每个IO线程都有一个属于自己的PoolThreadCache. 当PoolThreadCache无可用的内存时, 这个时候就会从PoolSubpage中查找空闲内存, 这个时候就要加锁了, 因为Arena是线程共享的, PoolSubpage也是线程共享的, 这个时候加锁使用synchronized(poolSubpage) {...} , 目前的加锁力度并不是很大, 只有两个IO线程申请相同大小的内存空间, 就会向相同的PoolSubpage申请空间, 这个时候这两个IO线程才会使用同一把锁, 如果两个IO线程都需要向PoolSubpage申请空间, 但是是在不同的PoolSubpage中申请空间, 那么它们使用的是不同的锁. 假如PoolSubpage也没有适合的空闲空间, 那么就需要向Chunk申请了, 这个时候, 如果两个IO线程共享的是同一个Arean, 那么如果它们都需要向Chunk申请空间, 那么它们使用相同的锁, 即synchronized(this) {...}, 这里的this就是Arena.

    此篇文章只是简单说了下, 在Netty中在申请内存空间的时候可能会存在加锁的情况, 以及申请内存的大概流程, 更详细的内存申请会在接下来的文章中说到, 而且内存申请是比较难理解的一块内容, 希望通过我的解释, 到时候能让你有所收获. 为今天的你, 加个油!

    相关文章

      网友评论

          本文标题:Netty中真的没有使用锁吗?

          本文链接:https://www.haomeiwen.com/subject/qpixkltx.html