NioEventLoop-run方法完整学习笔记

Netty源码分析:NioEventLoop启动以及其IO操作和Task任务的处理 Netty源码分析:NioEventLoop类的execute、startThread、run方法在上篇博文分析服务端启动的过程中,我们遇到了如下的代码片段, if (eventLoop.inEventLoop()) {//判断当前线程是否为该EventLoop中拥有的线程,如果是,则直接注册,如果不是,则添加一个任务到该线程中 register 阅读详情

NioEventLoop.run() 方法完整学习笔记

本文档记录了我对 Netty 源码中 NioEventLoop.run() 方法的完整学习过程,包含所有疑问和解答。

目录

  1. 学习背景
  2. 方法整体结构
  3. 核心概念理解
  4. 方法详细解析
  5. 完整执行流程
  6. 实战案例分析
  7. 学习总结

一、学习背景

1.1 run() 方法在 Netty 中的位置

Netty 启动流程:
    ↓
创建 NioEventLoopGroup
    ↓
创建多个 NioEventLoop
    ↓
第一次提交任务时,启动 EventLoop 线程
    ↓
EventLoop 线程执行 run() 方法(死循环)← 我们要学习的
    ↓
不断处理 IO 事件和任务

1.2 为什么要学习这个方法?

run() 方法是 Netty 的心脏,理解它就理解了 Netty 的核心工作机制。

形象比喻:

如果把 Netty 比作一个餐厅:
- NioEventLoopGroup 是餐厅
- NioEventLoop 是服务员
- run() 方法是服务员的工作流程

理解 run() 方法 = 理解服务员如何工作

二、方法整体结构

2.1 完整源码

@Override
protected void run() {
    for (;;) {  // 死循环,直到 EventLoop 关闭
        try {
            try {
                // ========== 第一阶段:Select 策略选择 ==========
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                        
                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO
                        
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));
                        
                        // wakenUp 机制:确保不会错过唤醒
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                        
                    default:
                }
            } catch (IOException e) {
                rebuildSelector0();
                handleLoopException(e);
                continue;
            }
            
            // ========== 第二阶段:处理 IO 事件和任务 ==========
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;  // 默认 50
            
            if (ioRatio == 100) {
                // 不限制任务执行时间
                try {
                    processSelectedKeys();  // 处理 IO 事件
                } finally {
                    runAllTasks();          // 处理所有任务
                }
            } else {
                // 限制任务执行时间
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();  // 处理 IO 事件
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        
        // ========== 第三阶段:关闭检查 ==========
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;  // 退出循环,线程结束
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

2.2 代码结构图

for (;;) {  // 死循环
    │
    ├─ 第一阶段:Select 策略选择
    │   ├─ hasTasks() 检查任务队列
    │   ├─ calculateStrategy() 决定策略
    │   ├─ select() 或 selectNow()
    │   └─ wakenUp 机制处理
    │
    ├─ 第二阶段:处理 IO 事件和任务
    │   ├─ processSelectedKeys() 处理 IO 事件
    │   └─ runAllTasks() 处理普通任务
    │       └─ 根据 ioRatio 控制时间
    │
    └─ 第三阶段:关闭检查
        └─ isShuttingDown() 检查是否需要关闭
}

2.3 三大核心职责

run() 方法的三大职责:

1. 监听 IO 事件
   - 使用 Selector 监听网络事件
   - 根据是否有任务选择阻塞或非阻塞

2. 处理 IO 事件
   - 读取网络数据
   - 写入网络数据
   - 接受新连接
   - 完成连接

3. 处理普通任务
   - 执行用户提交的任务
   - 执行 Netty 内部任务
   - 执行定时任务

三、核心概念理解

3.1 两种任务的区别

这是我学习过程中最大的疑惑点,现在终于理解了。

IO 事件 vs 普通任务
特性IO 事件普通任务
是什么网络相关的事件业务逻辑任务
来源Selector(JDK NIO)taskQueue(Netty)
触发方式网络事件触发代码主动提交
处理方法processSelectedKeys()runAllTasks()
处理内容读、写、连接、接受任意 Runnable
比喻服务客人(主要工作)做杂活(额外工作)
实际例子对比

IO 事件示例:

// 客户端发送数据
客户端:channel.writeAndFlush("Hello");

// 服务端 EventLoop
selector.select()  // 检测到 OP_READ 事件
    ↓
processSelectedKeys()  // 处理 IO 事件
    ↓
unsafe.read()  // 从 Socket 读取数据
    ↓
读取到:"Hello"
    ↓
触发 pipeline.fireChannelRead("Hello")

普通任务示例:

// 用户提交任务
eventLoop.execute(() -> {
    System.out.println("这是一个普通任务");
});

// EventLoop 处理
runAllTasks()  // 处理普通任务
    ↓
从 taskQueue 取出任务
    ↓
执行任务
    ↓
输出:"这是一个普通任务"

3.2 Select 策略理解

这是我第二个疑惑点,现在理解了。

为什么要区分有无任务?
核心问题:EventLoop 什么时候可以阻塞?

情况 A:任务队列为空
    ├─ 没有任务要处理
    ├─ 可以阻塞等待 IO 事件
    ├─ 使用 select()(阻塞)
    └─ 节省 CPU 资源

情况 B:任务队列不为空
    ├─ 有任务等待处理
    ├─ 不能阻塞,要赶紧处理任务
    ├─ 使用 selectNow()(非阻塞)
    └─ 快速检查 IO 事件后去处理任务
决策流程
hasTasks()  // 检查任务队列
    ↓
    ├─ false(没有任务)
    │   ↓
    │   calculateStrategy() 返回 SelectStrategy.SELECT
    │   ↓
    │   进入 SELECT 分支
    │   ↓
    │   select()  // 阻塞等待 IO 事件
    │
    └─ true(有任务)
        ↓
        calculateStrategy() 返回 selectNow() 的结果
        ↓
        进入 default 分支
        ↓
        selectNow()  // 非阻塞,立即返回

3.3 wakenUp 机制理解

这是我第三个疑惑点,也是最难理解的。

为什么需要 wakenUp?

问题场景:竞态条件

时间线:

T1: EventLoop 线程准备调用 select()
    准备阻塞...

T2: 外部线程提交任务
    execute(task)
    addTask(task)  // 任务已加入队列
    selector.wakeup()  // 想要唤醒 EventLoop

T3: EventLoop 线程调用 select()
    selector.select()  // 开始阻塞

问题:
    如果 T2 的 wakeup() 在 T3 的 select() 之前调用
    那么 wakeup() 的效果会被"浪费"
    select() 可能无法被唤醒
    任务得不到及时处理
wakenUp 如何解决?
// ===== EventLoop 线程 =====

// 步骤 1:设置 wakenUp = false
wakenUp.getAndSet(false);

// 步骤 2:调用 select(可能阻塞)
select();

// 步骤 3:检查 wakenUp
if (wakenUp.get()) {
    // 如果为 true,说明有人想唤醒
    selector.wakeup();  // 再唤醒一次
}


// ===== 外部线程 =====

// 步骤 A:提交任务
execute(task);
addTask(task);

// 步骤 B:尝试唤醒
if (wakenUp.compareAndSet(false, true)) {
    // 成功设置为 true,调用 wakeup
    selector.wakeup();
}
完整时间线分析

场景 1:wakeup() 在 select() 之前

T1: EventLoop: wakenUp = false
T2: 外部线程: wakenUp = true, selector.wakeup()
T3: EventLoop: select() 立即返回
T4: EventLoop: 检查 wakenUp = true, 再次 wakeup()
结果:✓ 正常工作

场景 2:wakeup() 在 select() 之后

T1: EventLoop: wakenUp = false
T2: EventLoop: select() 阻塞
T3: 外部线程: wakenUp = true, selector.wakeup()
T4: EventLoop: select() 被唤醒返回
T5: EventLoop: 检查 wakenUp = true, 再次 wakeup()
结果:✓ 正常工作

场景 3:多次提交任务

T1: EventLoop: wakenUp = false
T2: EventLoop: select() 阻塞
T3: 外部线程 A: wakenUp = true, selector.wakeup()
T4: EventLoop: select() 返回
T5: 外部线程 B: wakenUp 已经是 true,CAS 失败,不调用 wakeup()
T6: EventLoop: 检查 wakenUp = true, 再次 wakeup()(确保 B 的任务也能处理)
结果:✓ 正常工作

3.4 ioRatio 机制理解

这是我第四个疑惑点。

ioRatio 是什么?

ioRatio 控制 IO 事件处理和普通任务处理的时间比例。

final int ioRatio = this.ioRatio;  // 默认 50

if (ioRatio == 100) {
    // 不限制任务执行时间
    processSelectedKeys();
    runAllTasks();
} else {
    // 限制任务执行时间
    long ioStartTime = System.nanoTime();
    processSelectedKeys();
    long ioTime = System.nanoTime() - ioStartTime;
    
    // 根据 IO 时间计算任务执行时间
    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
时间分配计算
ioRatio = 50(默认):
    IO 时间 : 任务时间 = 50 : 50 = 1 : 1
    
    如果 IO 花了 100ms
    任务最多执行:100 * (100 - 50) / 50 = 100ms

ioRatio = 70:
    IO 时间 : 任务时间 = 70 : 30
    
    如果 IO 花了 100ms
    任务最多执行:100 * (100 - 70) / 70 ≈ 43ms

ioRatio = 30:
    IO 时间 : 任务时间 = 30 : 70
    
    如果 IO 花了 100ms
    任务最多执行:100 * (100 - 30) / 30 ≈ 233ms

ioRatio = 100:
    不限制任务执行时间
    处理完所有 IO 事件
    然后处理完所有任务
为什么需要 ioRatio?
平衡两种工作:

只处理 IO 事件:
    ✗ 任务队列会堆积
    ✗ 业务逻辑得不到执行
    ✗ 可能导致内存溢出

只处理任务:
    ✗ IO 事件得不到及时处理
    ✗ 网络数据会丢失或延迟
    ✗ 客户端可能超时

通过 ioRatio 平衡:
    ✓ IO 事件及时处理
    ✓ 任务也能执行
    ✓ 两者都不会饿死

四、方法详细解析

4.1 核心方法详解

方法 1:hasTasks()
protected boolean hasTasks() {
    assert inEventLoop();
    return !taskQueue.isEmpty();
}

作用: 检查任务队列中是否有任务。

返回值:

  • true:有任务等待
  • false:没有任务

使用场景:

// 决定使用哪种 select 策略
hasTasks() ? selectNow() : select();

方法 2:selectStrategy.calculateStrategy()
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) {
    return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}

作用: 根据是否有任务,决定使用哪种 select 策略。

参数:

  • selectSupplier:一个函数,调用时执行 selectNow()
  • hasTasks:是否有任务

返回值:

  • SelectStrategy.SELECT (-1):没有任务,使用阻塞式 select
  • >= 0:有任务,使用非阻塞式 selectNow(),返回准备好的 Channel 数量

selectSupplier 的实现:

private final IntSupplier selectNowSupplier = new IntSupplier() {
    @Override
    public int get() throws Exception {
        return selectNow();  // 调用 selectNow() 方法
    }
};

方法 3:SelectStrategy 常量
public interface SelectStrategy {
    int SELECT = -1;      // 需要阻塞式 select
    int CONTINUE = -2;    // 跳过本次循环
    int BUSY_WAIT = -3;   // 忙等待(NIO 不支持)
}

实际使用情况:

常见返回值:
├─ SELECT (-1)     ✓ 没任务时返回
└─ >= 0            ✓ 有任务时,selectNow() 的返回值

很少见返回值:
├─ CONTINUE (-2)   ✗ 几乎不用(为扩展性预留)
└─ BUSY_WAIT (-3)  ✗ NIO 不支持(fall-through 到 SELECT)

重要说明:

  • selectNow() 永远不会返回负数
  • selectNow() 只返回 >= 0 的数字
  • CONTINUE 和 BUSY_WAIT 是为了让自定义 SelectStrategy 有更多选择

方法 4:select() - 阻塞式等待
private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    try {
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
        
        for (;;) {
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            
            // 如果超时,执行一次 selectNow 后退出
            if (timeoutMillis <= 0) {
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }
            
            // 如果有任务进来了,立即返回
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                selector.selectNow();
                selectCnt = 1;
                break;
            }
            
            // 阻塞等待,最多等待 timeoutMillis 毫秒
            int selectedKeys = selector.select(timeoutMillis);
            selectCnt++;
            
            // 如果有 IO 事件、被唤醒、有任务、正在关闭,就退出
            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                break;
            }
            
            currentTimeNanos = System.nanoTime();
        }
    } catch (CancelledKeyException e) {
        // ...
    }
}

作用: 阻塞等待 IO 事件。

什么时候调用: 任务队列为空时。

会阻塞多久:

  • 直到有 IO 事件发生
  • 或被 selector.wakeup() 唤醒
  • 或超时(根据定时任务计算)
  • 或有新任务提交

实际例子:

// 服务端等待客户端连接

1. 任务队列为空
   hasTasks() = false
   
2. 调用 select()
   selector.select(timeoutMillis)  // 阻塞在这里
   
3. 等待中...
   EventLoop 线程休息,不消耗 CPU
   
4. 客户端发起连接
   Selector 检测到 OP_ACCEPT 事件
   
5. select() 返回
   返回值 = 1(有 1 个 Channel 准备好了)
   
6. 继续执行后续代码

方法 5:selectNow() - 非阻塞检查
int selectNow() throws IOException {
    return selector.selectNow();
}

作用: 立即检查是否有 IO 事件,不阻塞。

什么时候调用: 任务队列不为空时。

返回值: 准备好的 Channel 数量(0 或正数)。

与 select() 的对比:

select():
    会阻塞
    等待 IO 事件
    适合没有任务时使用
    节省 CPU

selectNow():
    不阻塞
    立即返回
    适合有任务时使用
    快速检查 IO 后去处理任务

实际例子:

// 有任务要处理

1. 有人提交了任务
   execute(() -> System.out.println("任务"));
   taskQueue = [任务]
   
2. hasTasks() = true
   
3. 调用 selectNow()
   int n = selector.selectNow();  // 立即返回,不阻塞
   
4. 假设返回 0(没有 IO 事件)
   
5. 继续执行
   processSelectedKeys();  // 没有 IO 事件要处理
   runAllTasks();          // 处理任务队列中的任务

方法 6:wakenUp.getAndSet(false)
select(wakenUp.getAndSet(false));

wakenUp 是什么:

private final AtomicBoolean wakenUp = new AtomicBoolean();

getAndSet(false) 的作用:

  • 获取 wakenUp 的当前值
  • 然后将 wakenUp 设置为 false
  • 返回之前的值

为什么要这样做:

// 在调用 select() 之前,先设置 wakenUp = false
boolean oldWakenUp = wakenUp.getAndSet(false);
select(oldWakenUp);

// 目的:
// 1. 标记"我要开始 select 了"
// 2. 如果有人想唤醒我,可以设置 wakenUp = true
// 3. select 返回后,我会检查 wakenUp 是否为 true

完整流程:

// EventLoop 线程
wakenUp.getAndSet(false);  // wakenUp = false
selector.select();         // 开始阻塞

// 外部线程提交任务
execute(task);
wakeup();
    wakenUp.compareAndSet(false, true);  // wakenUp = true
    selector.wakeup();                   // 唤醒 selector

// EventLoop 线程从 select 返回
if (wakenUp.get()) {  // 检查 wakenUp,发现是 true
    selector.wakeup();  // 再唤醒一次,确保安全
}

方法 7:wakeup()
protected void wakeup(boolean inEventLoop) {
    if (!inEventLoop || state == ST_SHUTTING_DOWN) {
        taskQueue.offer(WAKEUP_TASK);
    }
}

作用: 唤醒可能正在阻塞的 EventLoop 线程。

什么时候调用: 外部线程提交任务时。

为什么需要唤醒:

// 场景:EventLoop 正在 select() 中阻塞

EventLoop 线程:
    selector.select();  // 阻塞等待 IO 事件

外部线程:
    execute(task);  // 提交了一个任务
    
问题:
    EventLoop 正在阻塞,不知道有新任务
    需要唤醒它,让它去处理任务
    
解决:
    wakeup();
    selector.wakeup();  // 唤醒 selector

方法 8:processSelectedKeys() - 处理 IO 事件
private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

private void processSelectedKeysOptimized() {
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        selectedKeys.keys[i] = null;  // 清空,帮助 GC
        
        final Object a = k.attachment();
        
        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }
    }
}

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    
    try {
        int readyOps = k.readyOps();
        
        // 1. 处理连接完成事件
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            unsafe.finishConnect();
        }
        
        // 2. 处理写事件
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            ch.unsafe().forceFlush();
        }
        
        // 3. 处理读事件或接受事件
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

作用: 处理网络 IO 事件。

处理的事件类型:

├─ OP_ACCEPT  (16)  // 服务端接受新连接
├─ OP_CONNECT (8)   // 客户端连接完成
├─ OP_READ    (1)   // 有数据可读
└─ OP_WRITE   (4)   // 可以写数据

详细处理流程:

1. OP_READ 事件处理:

客户端发送数据:
    ↓
Selector 检测到 OP_READ 事件
    ↓
processSelectedKeys()
    ↓
找到对应的 Channel
    ↓
检测到 OP_READ 事件
    ↓
unsafe.read()
    ↓
从 Socket 读取数据到 ByteBuf
    ↓
触发 pipeline.fireChannelRead(msg)
    ↓
数据流经 pipeline 中的 handlers
    ↓
Decoder → BusinessHandler → Encoder

2. OP_WRITE 事件处理:

写缓冲区满了:
    ↓
注册 OP_WRITE 事件
    ↓
等待缓冲区可写
    ↓
Selector 检测到 OP_WRITE 事件
    ↓
processSelectedKeys()
    ↓
检测到 OP_WRITE 事件
    ↓
ch.unsafe().forceFlush()
    ↓
将数据从 Netty 缓冲区写入 Socket 缓冲区

3. OP_ACCEPT 事件处理:

客户端发起连接:
    ↓
Selector 检测到 OP_ACCEPT 事件
    ↓
processSelectedKeys()
    ↓
检测到 OP_ACCEPT 事件
    ↓
unsafe.read()  // 对于 ServerSocketChannel,read 就是 accept
    ↓
接受新连接,创建 SocketChannel
    ↓
创建 NioSocketChannel
    ↓
注册到 worker EventLoop

4. OP_CONNECT 事件处理:

客户端发起连接:
    ↓
connect() 返回 false(连接未完成)
    ↓
注册 OP_CONNECT 事件
    ↓
等待连接完成
    ↓
Selector 检测到 OP_CONNECT 事件
    ↓
processSelectedKeys()
    ↓
检测到 OP_CONNECT 事件
    ↓
unsafe.finishConnect()
    ↓
完成连接,触发 channelActive 事件

方法 9:runAllTasks() - 处理普通任务(续)


```java
protected boolean runAllTasks(long timeoutNanos) {
    // 1. 从定时任务队列取出到期的任务
    fetchFromScheduledTaskQueue();
    
    // 2. 从任务队列取第一个任务
    Runnable task = pollTask();
    if (task == null) {
        afterRunningAllTasks();
        return false;
    }
    
    // 3. 计算截止时间
    final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
    long runTasks = 0;
    long lastExecutionTime;
    
    // 4. 循环执行任务
    for (;;) {
        safeExecute(task);  // 执行任务
        runTasks++;
        
        // 每 64 个任务检查一次时间(nanoTime() 开销大)
        if ((runTasks & 0x3F) == 0) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            if (lastExecutionTime >= deadline) {
                break;  // 超时,退出
            }
        }
        
        task = pollTask();
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }
    
    afterRunningAllTasks();
    this.lastExecutionTime = lastExecutionTime;
    return true;
}

作用: 处理普通任务队列中的任务。

任务来源:

普通任务的来源:
├─ 用户代码提交:eventLoop.execute(task)
├─ Netty 内部任务:register、bind、connect、write
├─ 定时任务:eventLoop.schedule(task, delay, unit)
└─ Handler 中提交:ctx.executor().execute(task)

执行流程:

1. fetchFromScheduledTaskQueue()
   从定时任务队列取出到期的任务
   放入普通任务队列
   
2. pollTask()
   从任务队列取出一个任务
   
3. safeExecute(task)
   执行任务(捕获异常,不影响后续任务)
   
4. 检查时间
   每 64 个任务检查一次
   如果超时,停止执行
   
5. 继续取下一个任务
   直到队列为空或超时

为什么每 64 个任务才检查时间?

// nanoTime() 是一个相对昂贵的系统调用
// 每次调用都有开销
// 所以不是每个任务都检查,而是每 64 个任务检查一次

if ((runTasks & 0x3F) == 0) {
    // runTasks & 0x3F 等价于 runTasks % 64
    // 当 runTasks = 0, 64, 128, 192... 时,条件成立
    lastExecutionTime = ScheduledFutureTask.nanoTime();
    if (lastExecutionTime >= deadline) {
        break;
    }
}

实际例子:

// 场景:处理多个任务

任务队列:[register0, write, 业务任务1, 业务任务2]

runAllTasks(100ms)  // 最多执行 100ms
    ↓
取出 register0
    ↓
执行 register0(将 Channel 注册到 Selector)
    ↓
取出 write
    ↓
执行 write(写数据到 Socket)
    ↓
取出业务任务1
    ↓
执行业务任务1
    ↓
取出业务任务2
    ↓
执行业务任务2
    ↓
检查时间,已经用了 95ms
    ↓
取下一个任务,队列空了
    ↓
结束

五、完整执行流程

5.1 一次完整循环的流程图

【循环开始】
    ↓
┌─────────────────────────────────────┐
│ 1. 检查任务队列                      │
│    hasTasks()                        │
│    ├─ false → 没有任务               │
│    └─ true  → 有任务                 │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 2. 决定 Select 策略                  │
│    calculateStrategy()               │
│    ├─ 没任务 → SELECT (-1)           │
│    └─ 有任务 → selectNow() 的返回值  │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 3. 执行 Select                       │
│    ├─ SELECT 分支                    │
│    │   ├─ wakenUp.getAndSet(false)  │
│    │   ├─ select() 阻塞等待          │
│    │   └─ 检查 wakenUp,可能再唤醒   │
│    └─ default 分支                   │
│        └─ selectNow() 已执行         │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 4. 处理 IO 事件                      │
│    processSelectedKeys()             │
│    ├─ 遍历所有准备好的 Channel       │
│    ├─ 检查事件类型                   │
│    ├─ OP_READ → 读数据               │
│    ├─ OP_WRITE → 写数据              │
│    ├─ OP_ACCEPT → 接受连接           │
│    └─ OP_CONNECT → 完成连接          │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 5. 处理普通任务                      │
│    runAllTasks()                     │
│    ├─ 从定时任务队列取到期任务        │
│    ├─ 从任务队列取任务               │
│    ├─ 执行任务                       │
│    ├─ 每 64 个任务检查时间           │
│    └─ 超时或队列空则停止             │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 6. 检查是否关闭                      │
│    isShuttingDown()                  │
│    ├─ 是 → closeAll() → return      │
│    └─ 否 → 继续循环                  │
└─────────────────────────────────────┘
    ↓
【回到循环开始】

5.2 实战案例:处理客户端请求

场景:客户端发送 “Hello”,服务端回复 “World”

【第 1 次循环】

1. 检查任务队列
   taskQueue = []
   hasTasks() = false
   
2. 决定策略
   calculateStrategy() 返回 SELECT
   
3. 执行 Select
   wakenUp.getAndSet(false)  // wakenUp = false
   selector.select()         // 阻塞等待
   
   ... 等待中 ...
   
   客户端发送 "Hello"
   Selector 检测到 OP_READ 事件
   select() 返回
   
4. 检查 wakenUp
   wakenUp.get() = false
   不需要额外唤醒
   
5. 处理 IO 事件
   ioStartTime = 当前时间
   processSelectedKeys()
       ↓
   找到对应的 Channel
       ↓
   检测到 OP_READ 事件
       ↓
   unsafe.read()
       ↓
   从 Socket 读取数据:"Hello"
       ↓
   触发 pipeline.fireChannelRead("Hello")
       ↓
   数据流经 pipeline:
       Decoder → 解码 "Hello"
       BusinessHandler → 处理业务逻辑
           ctx.writeAndFlush("World");  // 回复客户端
           这会提交一个 write 任务到 taskQueue
       Encoder → 编码 "World"
   
   ioTime = 当前时间 - ioStartTime = 10ms
   
6. 处理任务队列
   runAllTasks(10 * (100 - 50) / 50)
   runAllTasks(10ms)  // 最多执行 10ms
       ↓
   取出 write 任务
       ↓
   执行 write
       ↓
   将 "World" 写入 Socket 缓冲区
       ↓
   如果缓冲区满了,注册 OP_WRITE 事件
       ↓
   检查时间,还没超时
       ↓
   取下一个任务,队列空了
       ↓
   结束
   
7. 检查关闭
   isShuttingDown() = false
   不需要关闭
   
【循环结束,回到步骤 1】


【第 2 次循环】

1. 检查任务队列
   taskQueue = []
   hasTasks() = false
   
2. 决定策略
   calculateStrategy() 返回 SELECT
   
3. 执行 Select
   wakenUp.getAndSet(false)
   selector.select()  // 阻塞等待下一个事件
   
... 继续循环 ...

5.3 实战案例:有任务时的处理

场景:业务线程提交任务

【循环进行中】

EventLoop 线程正在 select() 中阻塞:
    selector.select();  // 等待 IO 事件

业务线程提交任务:
    eventLoop.execute(() -> {
        System.out.println("业务任务");
    });
    
    execute() 方法内部:
        addTask(task);  // 任务加入队列
        wakeup();
            wakenUp.compareAndSet(false, true)  // wakenUp = true
            selector.wakeup()  // 唤醒 selector

EventLoop 线程被唤醒:
    select() 返回
    
    if (wakenUp.get()) {  // wakenUp = true
        selector.wakeup();  // 再唤醒一次
    }
    
    processSelectedKeys();  // 没有 IO 事件
    
    runAllTasks();
        取出业务任务
        执行:System.out.println("业务任务")
        输出:"业务任务"

【下一次循环】

1. 检查任务队列
   taskQueue = []
   hasTasks() = false
   
2. 继续 select() 阻塞...

六、学习总结

6.1 核心要点回顾

1. run() 方法是一个死循环

for (;;) {
    监听 IO 事件
    处理 IO 事件
    处理普通任务
    检查是否关闭
}

2. 两种任务的区别

IO 事件:
    - 网络相关的事件
    - 由 Selector 监听
    - processSelectedKeys() 处理

普通任务:
    - 业务逻辑任务
    - 由代码提交到 taskQueue
    - runAllTasks() 处理

3. Select 策略

有任务:selectNow()(不阻塞)
没任务:select()(阻塞)

4. wakenUp 机制

作用:确保任务提交时能正确唤醒 EventLoop
方法:通过 AtomicBoolean 标记 + 双重唤醒

5. ioRatio 机制

作用:平衡 IO 事件和普通任务的时间分配
默认:50(1:1 的时间比例)

6.2 学习过程中的疑惑和解答

疑惑 1:processSelectedKeys() 和 runAllTasks() 有什么区别?

解答:

  • processSelectedKeys() 处理网络 IO 事件(读、写、连接、接受)
  • runAllTasks() 处理普通任务(业务逻辑、内部任务)
  • 两者处理的是完全不同类型的工作

疑惑 2:为什么要区分有无任务?

解答:

  • 有任务时不能阻塞,要赶紧处理任务
  • 没任务时可以阻塞,节省 CPU
  • 通过 selectNow() 和 select() 实现

疑惑 3:wakenUp 机制是干什么的?

解答:

  • 解决并发场景下的唤醒丢失问题
  • 确保任务提交时能正确唤醒 EventLoop
  • 通过标记 + 双重唤醒实现

疑惑 4:SelectStrategy.CONTINUE 和 BUSY_WAIT 是什么?

解答:

  • 这两个常量很少使用
  • 是为了扩展性预留的
  • selectNow() 不会返回这些值

疑惑 5:ioRatio 是如何工作的?

解答:

  • 控制 IO 事件和普通任务的时间比例
  • 通过计算公式:taskTime = ioTime * (100 - ioRatio) / ioRatio
  • 防止某一方饿死

6.3 关键设计思想(续)

3. 任务队列化

所有任务进入队列
串行执行,保证顺序
线程安全,无需加锁

4. 时间平衡

通过 ioRatio 平衡 IO 和任务
防止某一方饿死
保证系统稳定运行

5. 懒加载

线程不是在构造时创建
而是在第一次提交任务时创建
节省资源

6.4 性能优化技巧

1. 减少系统调用

// 每 64 个任务才检查一次时间
if ((runTasks & 0x3F) == 0) {
    lastExecutionTime = ScheduledFutureTask.nanoTime();
}

// 原因:nanoTime() 是系统调用,有开销

2. 优化的 SelectedKeys

// Netty 优化了 Selector 的 selectedKeys
// 使用数组代替 HashSet
// 提高遍历性能
private SelectedSelectionKeySet selectedKeys;

3. 避免不必要的唤醒

// 通过 wakenUp 标记避免重复唤醒
if (wakenUp.compareAndSet(false, true)) {
    selector.wakeup();  // 只有第一个线程会唤醒
}

4. 批量处理任务

// 不是来一个任务处理一个
// 而是批量处理,提高效率
runAllTasks(timeoutNanos);

6.5 常见问题和解答

Q1: 为什么 run() 方法是死循环?

A: 因为 EventLoop 需要持续运行,不断处理 IO 事件和任务,直到被关闭。

Q2: 如果任务队列一直有任务,IO 事件会不会得不到处理?

A: 不会。通过 ioRatio 机制保证 IO 事件和任务都能得到处理。

Q3: 如果 IO 事件一直很多,任务会不会得不到处理?

A: 不会。processSelectedKeys() 处理完所有 IO 事件后,一定会执行 runAllTasks()。

Q4: wakenUp 为什么要"再唤醒一次"?

A: 因为 selector.wakeup() 的效果只对"下一次" select() 有效,再唤醒一次确保不会错过。

Q5: 为什么不用 ioRatio = 100?

A: ioRatio = 100 会处理完所有任务,可能导致任务执行时间过长,影响 IO 事件的及时处理。

Q6: EventLoop 线程什么时候会结束?

A: 当调用 shutdownGracefully() 并且所有任务处理完毕后,run() 方法会 return,线程结束。

6.6 与 JDK NIO 的对比

JDK NIO 的写法:

Selector selector = Selector.open();
// 注册 Channel...

while (true) {
    selector.select();  // 阻塞等待
    
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> it = keys.iterator();
    
    while (it.hasNext()) {
        SelectionKey key = it.next();
        it.remove();
        
        if (key.isReadable()) {
            // 处理读事件
        }
        if (key.isWritable()) {
            // 处理写事件
        }
    }
}

Netty 的改进:

1. 增加了任务队列
   - JDK NIO 只能处理 IO 事件
   - Netty 可以处理 IO 事件 + 普通任务

2. 优化了 select 策略
   - JDK NIO 总是阻塞
   - Netty 根据是否有任务决定是否阻塞

3. 增加了时间控制
   - JDK NIO 没有时间控制
   - Netty 通过 ioRatio 控制时间分配

4. 优化了数据结构
   - JDK NIO 使用 HashSet
   - Netty 使用数组,性能更好

5. 增加了唤醒机制
   - JDK NIO 需要手动管理唤醒
   - Netty 通过 wakenUp 自动管理

6.7 学习建议

1. 先理解整体,再深入细节

第一遍:理解 run() 方法的整体流程
第二遍:理解每个方法的作用
第三遍:理解设计思想和优化技巧

2. 结合实际场景理解

不要只看代码
要想象实际的网络通信场景
比如:客户端发送数据,服务端如何处理

3. 对比 JDK NIO

理解 Netty 在 JDK NIO 基础上做了哪些改进
为什么要这样改进

4. 动手实践

写一个简单的 Echo 服务器
Debug 跟踪 run() 方法的执行
观察每次循环的状态变化

5. 画图理解

画流程图
画时序图
画状态转换图

七、附录

7.1 完整的方法调用链

EventLoop 线程启动
    ↓
doStartThread()
    ↓
executor.execute(runnable)
    ↓
thread = Thread.currentThread()
    ↓
run()  ← 我们学习的方法
    ↓
for (;;) {
    ├─ hasTasks()
    ├─ calculateStrategy()
    ├─ select() 或 selectNow()
    ├─ processSelectedKeys()
    │   └─ processSelectedKey()
    │       ├─ unsafe.finishConnect()
    │       ├─ unsafe.forceFlush()
    │       └─ unsafe.read()
    ├─ runAllTasks()
    │   ├─ fetchFromScheduledTaskQueue()
    │   ├─ pollTask()
    │   └─ safeExecute(task)
    └─ isShuttingDown()
}

7.2 关键变量说明

// Selector 相关
private Selector selector;                    // JDK 的 Selector
private SelectedSelectionKeySet selectedKeys; // 优化的 selectedKeys

// 任务队列
private final Queue<Runnable> taskQueue;      // 普通任务队列
private final PriorityQueue scheduledTaskQueue; // 定时任务队列

// 唤醒机制
private final AtomicBoolean wakenUp;          // 唤醒标记

// 时间控制
private volatile int ioRatio = 50;            // IO 时间比例
private long lastExecutionTime;               // 上次执行时间

// 状态
private volatile int state = ST_NOT_STARTED;  // EventLoop 状态

7.3 相关类图

NioEventLoop
    ├─ extends SingleThreadEventLoop
    │   └─ extends SingleThreadEventExecutor
    │       └─ implements EventLoop
    │
    ├─ 持有 Selector
    ├─ 持有 taskQueue
    ├─ 持有 scheduledTaskQueue
    └─ 持有 Thread

八、总结

核心理解:

  1. run() 是一个死循环,不断处理 IO 事件和任务
  2. 两种任务:IO 事件(网络)和普通任务(业务)
  3. Select 策略:有任务不阻塞,没任务阻塞
  4. wakenUp 机制:确保任务提交时能正确唤醒
  5. ioRatio 机制:平衡 IO 和任务的时间分配

设计精髓:

  • 单线程模型,避免锁竞争
  • 事件驱动,节省 CPU
  • 任务队列化,保证顺序
  • 时间平衡,防止饿死
  • 性能优化,减少开销

学习收获:

  • 理解了 Netty 的核心工作机制
  • 学会了如何阅读复杂的源码
  • 掌握了高性能网络编程的设计思想
  • 提升了对并发编程的理解
netty源码分析6-NioEventLoop启动和run方法 分享内容如下 select启动跟踪 定时任务机制分析 NioEventLoopIO轮询方法分析 select启动跟踪 NioEventLoop中有循环select实现,是什么时候被调用的? 循环select由run方法实现,调用链如下图 6 ServerBootstrap(AbstractBootstrap<B,C>).initAndRegister() line... 阅读详情

相关推荐

Netty源码分析系列之NioEventLoop的执行流程

NioEventLoop作为一个NIO事件轮询器,到底在循环处理什么呢?JDK中臭名昭著的空轮询BUG,在netty中又是如何避免的呢?

天堂的博客 689

Netty源码三:NioEventLoop创建与run方法

接着会调用到子类NioServerSocketChannel的doReadMessage(readBuf), 调用完子类之后,会通过pipline.fireChannelRead,意思就是对于server端来说可读了,但是注意这时候传的是子类中创建的NioSocketChannel,说白了给server端一个NioSocketChannel,就是要创建新连接了。最终会调用到ServerBootstrapAcceptor的ChannelRead,具体看后面的ServerBootStrapAcceptor类。

一只小星星 1340

Netty4学习笔记(6)-- NioEventLoop继承层次结构

分析NioEventLoop继承层次结构

zxh的专栏 6734

Netty之NioEventLoop的run方法

hhh EventLoop

weixin_43257196 的博客 595

19-NioEventLoop IO事件处理

文章目录NioEventLoop IO事件处理一、NioEventLoop#run二、功能方法2.1 hasTasks2.2 select(booolean oldWakenUp)2.3 processSelectedKeys2.3.1 processSelectedKeys2.3.2 processSelectedKeysOptimized2.3.3 processSelectedKey(Sel...

my_momo_csdn的博客 879

Netty NioEventLoop源码解读

  NioEventLoop中维护了一个线程,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:I/O任务:即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。非IO任务:添加到taskQueue中的任务,如register...

everyok的博客 321

netty NioEventLoop中run()方法执行流程分析

NioEventLoop的run方法的执行过程 在创建线程对象的任务中调用了SingleThreadEventExecutor.this#run()方法,使NioEventLoop开始运行,开始处理任务。 NioEventLoop#run()方法如下所示,可以看到run()方法是一个无限循环,直到NioEventLoop被关闭。 run()方法中的逻辑主要分为以下三大步骤 select 选择任...

我是兴锅的博客 1615

badusb详细制作教程(两种方式实现远控)

badusb是一种几乎可以突破任何杀毒软件和防火墙的设备,堪称渗透测试利器 文章目录前言一、什么是badusb?二,badusb的优点三,badusb是如何攻击的?badusb原理HID攻击漏洞产生原因三、准备步骤1.设备2,编辑器的安装与设置四,编写代码(重点)第一种: SEToolkit + badusb实现PowerShell注入攻击第二种:Cobalt Strike + badusb 实现PowerShell注入攻击五,烧录六,badusb有多强大攻击场景总结 BadUSB是《黑客军团》中

Ghost_system的博客 1万+

Netty源码学习系列之5-NioEventLoop的run方法

NioEventLoop的run方法,是netty中最核心的方法,没有之一。在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端的连接事件和读写事件,在客户端时是读写事件)、单线程任务队列的处理(服务端的注册事件、客户端的connect事件等),当然还包括对NIO空轮询的规避、消息的编解码等。下面一起来探究一番,首先奉上run方法的源码: 1 protected void run() { 2 for (;;) { 3

xueqilily的博客 408

centos-stream-9上安装nvidia驱动和cuda-toolkit

centos-stream-9上安装nvidia驱动和cuda-toolkit

qq_19072921的博客 3022

从静态到动态:如何用Deformable 3DGS和4DGS实现高效动态场景建模

本文深入探讨了Deformable 3DGS和4DGS在动态场景建模中的技术突破,详细解析了这两种方法如何高效处理动态内容。Deformable 3DGS通过引入时间维度的变形场扩展传统3DGS,而4DGS则采用四维时空体素分解方法,两者均显著提升了动态场景建模的效率和质量,适用于虚拟现实、增强现实等应用场景。

weixin_29063035的博客 435

netty源码--NioEventLoop的run方法

netty框架源码分享

qq_41868309的博客 509

Linux文件操作四剑客

① /^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/ // 年-月-日 yyyy-MM-dd / yy-MM-dd 格式。⑥“^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$” //正浮点数。

wuds_158的博客 1611

Netty 线程工作机制—— NioEventLoop

这里会调用当前NioEventLoop工作,执行 eventLoop.execute,通过它的方法继承可以看到其实是使用SingleThreadEventLoop#execute方法NioEventLoop的父类体系很庞大,很多事情都有专门的父类去执行,比如这里的execute通过SingleThreadEventLoop去实现。......

qq_44587855的博客 970

【制作100个unity实战之2】实现一款简单的FPS游戏

你将学到: fps武器脚本 基础瞄准功能 僵尸敌人AI 巡逻范围 攻击间 搜寻半径 追击 枪声吸引周围的僵尸 击中僵直效果

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 3553

【虚幻引擎UE】UE5 四步骤实现AI漫游与对话行为(含工程源码)

【虚幻引擎UE】UE5 四步骤实现AI漫游与对话行为(含工程源码)

灵境引路人 3227

Netty中NioEventLoop源码分析

版本 本次源码分析基于Netty的版本为4.1 源码分析 NioEventLoop可以视为java中的一个线程,只不过NioEventLoop处理的事件,以及内部的处理逻辑会有所不同。先看一下类的继承关系: 可以看到NioEventLoop实现了很多接口,特别是EventLoop和ScheduledExecutorService,所以NioEventLoop不仅能实现普通的task,还能实现定时t...

Forest441的博客 1043

基于深度学习的树木种类识别

本文介绍了基于深度学习的树木种类识别技术。我们将通过深度学习模型和图像处理技术实现对树木的自动识别与分类。首先,我们将介绍深度学习的基本原理和相关算法。接着,我们将详细说明树木种类识别的数据准备、模型构建和训练过程。最后,我们将给出代码示例,帮助读者深入了解和实践该技术。

一键难忘的博客 2317

netty源码之NioEventLoopt的run方法详解

上一篇文章中我们了解了netty的启动过程,初步了解了NioEventLoop,本篇文章讲解NioEventLoop最核心的功能run()方法 run()方法怎么被调用? 由NioEventLoop的run()方法向前追溯我们看到是SingleThreadEventExecutor中调用了run()方法,我们在这一行代码打上一个断点,然后看下其调用链即可。 点开第二行,看到了熟悉的excute()方法 public void execute(Runnable task) { if (tas

Ethan_199402的博客 654

Unreal Engine开发:角色动画与状态机_动画混合:通过混合节点实现平滑动画过渡

动画混合是提升游戏动画质量的关键技术之一。Unreal Engine提供了多种混合节点,包括BlendSpace1D、Blend Two By Bool、Blend Two By Alpha和Sequencer,每种节点适用于不同的场景。通过合理使用这些节点,可以实现平滑、自然且高效的动画过渡效果。此外,遵循最佳实践和高级应用技巧,可以进一步优化动画质量和性能。希望本节内容对你在Unreal Engine中实现动画混合有所帮助,使你的游戏角色动作更加流畅和自然。

chenlz2007的博客 1247

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Netty源码分析---NioEventLoop的run方法详解

本文深入解析了Netty中NioEventLoop的核心run()方法实现。该方法采用无限循环结构,主要包含三个关键步骤:1)通过select()方法等待I/O事件,根据任务队列情况智能选择阻塞或非阻塞模式;2)使用processSelectedKeys()处理I/O事件;3)通过runAllTasks()执行任务队列中的任务。文章详细分析了select()方法的实现逻辑,包括SelectStrategy策略如何根据任务存在与否决定使用selectNow()或阻塞select(),以及wakeup机制的设计

java_rutu的博客 368

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

netty NioEventLoop的selectedKeys是在哪里修改的

问题产生的原因 查看netty源码时,理解NioEventLoop中的run方法主要负责轮询监听就绪的 I/O事件,流程就三个步骤: 1、轮询 Selector 选择器中已经注册的所有 Channel 的 I/O 事件。select()方法 2、处理已经准备就绪的 I/O 事件。 processSelectedKeys()方法 3、处理任务队列中的非 I/O 任务。runAllTasks()方法 其中第一步就是循环查找是否有需要处理的任务,当接收到任务时,则去处理。进入第二步时,代表存在任务。即进入proc

shanliang 2350

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

五、NioEventLoop启动

NioEventLoop执行概述: 1.何时被启动 当AbstractChannel.register()方法被执行时,该方法下有这样一段代码: if (eventLoop.inEventLoop()) { register0(promise); } else { try { ...

Alen_0076的博客 288

Netty 之 NioEventLoop 源码阅读

文章目录1. 概述2. EventExecutorGroup实例3. 源码3.1 构造函数3.1.1 DefaultEventLoopGroup 构造函数3.1.2 NioEventLoop 构造函数3.2 run 方法3.2.1 SelectStrategy3.2.2 select3.2.3 processSelect...

weixin_41306232的博客 219

Netty NioEventLoop执行逻辑

NioEventLoop 执行逻辑 方法: NioEventLoop.run() for(;; ) select() 检查是否有 IO 事件 processSelectedKeys() 处理上面检测出来的 IO 事件 runAllTasks() 处理异步任务队列 protected void run() { for (;;) { try { try { // 1. 调用 select() 查询是否有就绪的 I/O 事件

flyingzc的博客 302

从register任务看 netty NioEventLoop 的启动流程

结合源码流程,理解 NioEventLoop 的启动流程及核心循环

oatlmy 817
上一篇: Netty解码器深度解析-详细版
下一篇: NioEventLoop事件检测与触发机制总结
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值