当前位置: 首页 > news >正文

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析

ThreadPoolExecutor 状态

ThreadPoolExecutor 继承了 AbstractExecutorService,并实现了 ExecutorService 接口,用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态,后 29 位表示 Worker 数量。

public class ThreadPoolExecutor extends `AbstractExecutorService` {private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;private static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN   =  0 << COUNT_BITS;private static final int STOP       =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;private static int runStateOf(int c)     { return c & ~CAPACITY; }private static int workerCountOf(int c)  { return c & CAPACITY; }private static int ctlOf(int rs, int wc) { return rs | wc; }private static boolean runStateLessThan(int c, int s) {return c < s;}private static boolean runStateAtLeast(int c, int s) {return c >= s;}private static boolean isRunning(int c) {return c < SHUTDOWN;}......}

由源码中的常量定义可知,ThreadPoolExecutor 有 5 种线程池状态:

  • RUNNING:线程池接收新任务,会执行任务阻塞队列中的任务,ctl 前三位为 111
  • SHUTDOWN:线程池拒绝接收新任务,会执行任务阻塞队列中的任务,ctl 前三位为 000
  • STOP:线程池拒绝接收新任务,不会执行任务阻塞队列中的任务,尝试中断正在执行的任务,ctl 前三位为 001
  • TIDYING:所有任务被关闭,Worker 数量为 0,ctl 前三位为 010
  • TERMINATED:方法 terminated() 执行完毕,ctl 前三位为 011

通过 ctl 的结构可知,其前三位取值对应的线程池状态满足以下关系:

RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED

因此 runStateLessThan()runStateAtLeast()isRunning() 方法可以很方便的对线程池状态进行判断,不需要考虑当前 Worker 的具体数量。

img

执行任务源码分析

执行任务的方法为 execute(),其源码如下:

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();// 1.如果正在运行的线程数少于核心线程数,则尝试以给定的操作作为其第一个任务以启动一个新线程。addWorker()的调用以原子方式检查了 runState 和 workerCount,从而防止由于误报 false 而导致的线程增加if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 2.如果一个任务成功加入了阻塞队列,那么我们仍然需要仔细检查是否应该添加一个线程(因为自上次检查以来原有的线程可能已死亡)或自进入此方法以来,线程池已关闭。所以如果重新检查状态已停止,有必要则直接回滚入队操作,或者如果没有正在运行的线程,则启动一个新线程。if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (!isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 3.如果无法将任务加入阻塞队列,那么我们尝试添加一个新的线程。 如果失败了,那我们就知道任务线程池已经关闭或饱和,因此拒绝任务。else if (!addWorker(command, false))reject(command);
}

execute() 方法根据 Worker 的数量和线程池状态来决定是新建 Worker 来执行任务,还是将任务添加到任务阻塞队列中。任务无法成功添加到阻塞队列或者新建 Worker 来执行任务失败,则执行任务拒绝策略。

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (int c = ctl.get();;) {// 线程池不为 RUNNING 状态已停止工作,或为 SHUTDOWN 同时阻塞队列为空,则 Worker 创建失败if (runStateAtLeast(c, SHUTDOWN)&& (runStateAtLeast(c, STOP)|| firstTask != null|| workQueue.isEmpty()))return false;for (;;) {// 当前 Worker 数量大于 size,则拒绝创建 Worker。其中 core 为 true 对比核心线程数,core 为 false 则比对最大线程数。if (workerCountOf(c)>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))return false;// 以 CAS 方式将 Worker 数量加1,成功则跳出 retry 循环if (compareAndIncrementWorkerCount(c))break retry;// CAS 追加 Worker 数量失败,重新获取 ctl。c = ctl.get();  // 线程池状态改变,则需要基于新的线程池状态,重新执行外层循环来判断是否允许创建 Worker// 线程池状态不变,则继续执行内层循环,再次尝试以 CAS 方式增加 Worker 数量if (runStateAtLeast(c, SHUTDOWN))continue retry;}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {// 存储 Worker 的 HashSet 需要获取全局锁来保证添加 Worker 时的线程安全final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 在获取全局锁之后,再次获取线程池最新的状态int c = ctl.get();// 线程池状态为 RUNNING 或 SHUTDOWN 同时创建 Worker 的初始任务为 nullif (isRunning(c) ||(runStateLessThan(c, STOP) && firstTask == null)) {// 避免 Worker 的线程被重复启动(处于活动状态的线程无法再次启动)if (t.isAlive())throw new IllegalThreadStateException();workers.add(w);// 记录线程池存在过的最大 Worker 数int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;
}

addWorker() 方法中只有以下两种情况下可以创建 Worker:

  • 线程池状态为 RUNNIGNG
  • 线程池状态为 SHUTDOWN,且任务阻塞队列不为空,可以创建初始任务为 null 的 Worker

当 Worker 创建成功后,其线程就会启动,如果 Worker 创建失败或者线程启动失败,则会调用回滚方法 addWorkerFailed(),源码实现如下:

private void addWorkerFailed(Worker w) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (w != null)workers.remove(w);decrementWorkerCount();tryTerminate();} finally {mainLock.unlock();}
}

在这个过程中所添加的 Worker 实体,实现了 Runnable 接口,因此它实际上就是一个任务,其线程执行的任务就是它本身。所以在 addWorker() 方法中启动线程时,会调用 Worker 的 run() 方法,实际上内部就是调用了 runWorker() 方法,源码实现如下:

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;// 释放了锁,以支持外部中断任务w.unlock();// 是否属于被中断的状态标识boolean completedAbruptly = true;try {// 获取 Worker 当前任务,或者从阻塞队列中取出下一个任务while (task != null || (task = getTask()) != null) {w.lock();// 当线程池为 STOPPING 状态时,要确保当前线程是被中断的(为了确保 shutdownNow() 方法在中断任务时能正确处理)if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}
}

从循环的条件可以看出,Worker 启动后,会首先执行自己的初始任务,然后再去任务阻塞队列中获取任务。任务流程执行结束或被中断后,则会调用 processWorkerExit() 方法。当 Worker 任务执行异常,或获取的任务最终为 null 时,该方法会将当前 Worker 从集合中删除,并尝试终止线程池。processWorkerExit() 方法的源码实现如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {// 异常中断的任务,Worker 数量没有及时调整,需要修正if (completedAbruptly)decrementWorkerCount();final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {completedTaskCount += w.completedTasks;workers.remove(w);} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();int c = ctl.get();if (runStateLessThan(c, STOP)) {// 如果线程池仍在可运行状态,由异常中断的导致被移除的 Worker 需要新增一个 null 任务来替换,以保持核心线程池数目或者阻塞队列的状态一致if (!completedAbruptly) {          int min = allowCoreThreadTimeOut ? 0 : corePoolSize;if (min == 0 && ! workQueue.isEmpty())min = 1;if (workerCountOf(c) >= min)return; // replacement not needed}addWorker(null, false);}
}

总结下来,ThreadPoolExecutor 执行任务主要由以下几步:

  1. 判断当前线程池状态是否允许创建 Worker

    • 线程池状态为 RUNNIGNG,可以创建 Worker
    • 线程池状态为 SHUTDOWN,且任务阻塞队列不为空,可以创建初始任务为 null 的 Worker
  2. 按照以下规则判断如何添加 Worker(执行任务):

    • 如果 Worker 数量小于核心线程数,则创建 Worker 来执行任务
    • 如果 Worker 数量大于等于核心线程数,则将任务添加到任务阻塞队列
    • 如果任务阻塞队列已满,则创建 Worker 来执行任务
    • 如果 Worker 数量已经达到最大线程数,此时执行任务拒绝策略
  3. Worker启动自身持有的线程,并执行自身实现的任务。启动后先执行自己的初始任务,然后再取任务阻塞队列中的任务。

关闭线程池源码分析

关闭 ThreadPoolExecutor 的方法主要有 shutdown()shutdownNow()

shutdown()

停止接收新任务,会把阻塞队列中的任务执行完毕。

public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 检查相关的线程修改权限checkShutdownAccess();// 通过CAS将线程池状态修改为SHUTDOWNadvanceRunState(SHUTDOWN);// 中断空闲的WorkerinterruptIdleWorkers();// 为 ScheduledThreadPoolExecutor 提供的方法入口,根据指定的终止策略,中断和清除队列中所有任务onShutdown();} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();
}

空闲 Worker 的中断涉及到锁的获取,具体处理过程可以参考以下的源码:

private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;// 中断线程前,尝试获取Worker的锁if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}
}private void interruptIdleWorkers() {interruptIdleWorkers(false);
}

这里需要注意的是,Worker 除了实现了 Runnable 接口外,还继承了 AbstractQueuedSynchronizer,因此 Worker 本身还是一把锁,故有其自身实现锁的相关方法。以下是相关方法的实现:

public void lock()        { acquire(1); }
public boolean tryLock()  { return tryAcquire(1); }
public void unlock()      { release(1); }protected boolean tryAcquire(int unused) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;
}
protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);return true;
}

从源码中可以看出,Worker 的 lock() 方法调用了 AbstractQueuedSynchronizer 抽象类的 acquire() 方法,该方法内部又调用了 Worker 实现的 tryAcquire() 方法。tryAcquire() 方法通过 CAS 将锁的状态 STATE 从 0 设置为 1,由此可知 Worker 是一把不可重入锁。

正由于 Worker 是一把不可重入锁,正在执行任务的 Worker 是无法获取到锁的,只有线程池中没有执行任务的 Worker 才能被获取到锁,所以关闭线程池时对空闲 Worker 执行中断指令,实际上就是中断没有执行任务的空闲 Worker。正在执行任务的 Worker 在 shutdown() 方法被调用时不会被中断,而是继续执行完当前任务,随后再从任务阻塞队列中获取任务来执行,直到任务阻塞队列为空,紧接着当前 Worker 也会被删除。等到线程池中所有的 Worker 都被删除,以及任务阻塞队列任务清空后,线程池才会被终止掉。

总结下来,shutdown() 方法实现的内容就是:将线程池的状态设置为 SHUTDOWN,拒绝接收新的任务,等到线程池 Worker 数为 0,且任务阻塞队列为空时,关闭线程池。

shutdownNow()

停止接收新任务,中断当前所有的任务,并将线程池的状态置为 STOP。

public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 通过CAS方式将线程池状态设置为STOPadvanceRunState(STOP);// 中断所有WorkerinterruptWorkers();// 将任务阻塞队列中所有任务获取出来并返回tasks = drainQueue();} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();return tasks;
}private void interruptWorkers() {// 需要确保当前线程获取到了全局锁 this.mainLockfor (Worker w : workers)w.interruptIfStarted();
}

shutdownNow() 方法跟 shutdown() 方法的过程基本一致,在一些细节的具体实现上有所区别。首先会将线程池状态设置为 STOP,然后调用 interruptWorkers() 方法中断线程池中的所有 Worker,接着调用 tryTerminate() 方法尝试终止线程池,最后将任务阻塞队列中未被执行的任务返回。

shutdownNow() 方法调用后,线程池中的所有 Worker 都会被中断,包括正在执行任务的 Worker。也就是说 shutdownNow() 方法不会保证正在执行的任务会被安全的执行完毕,同时还会放弃任务阻塞队列中的所有任务。

tryTerminate()

前面多次提及的 tryTerminate() 方法可以确保线程池被正确的关闭,这里可以看看源码的具体实现:

final void tryTerminate() {for (;;) {int c = ctl.get();// 满足以下三种状态的线程池,不能被终止if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))return;// Worker数量不为0时,中断一个空闲的Worker;内部传播了shudown信号if (workerCountOf(c) != 0) {interruptIdleWorkers(ONLY_ONE);return;}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 将线程池状态设置为TIDYINGif (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {// 终止线程池terminated();} finally {// 将线程池状态设置为TERMINATEDctl.set(ctlOf(TERMINATED, 0));termination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}
}

线程池不能被终止的三种状态:

  • RUNNING - 正在执行任务
  • 大于或等于TIDYING - 正在进行终止流程或已经被终止
  • SHUTDOWN,但任务阻塞队列不为空 - 正在等待任务阻塞队列的任务被执行完

如果线程池不属于以上三种状态,此时可以通过中断一个空闲的 Worker,被中断的空闲 Worker 会在 getTask() 方法中返回 null,从而执行 processWorkerExit() 方法。在 processWorkerExit() 方法中,会删除当前的 Worker,又会再调用 tryTerminate() 方法,从而实现在所有空闲 Worker 之前传播 shutdown 信号。

以下是源码中提供的官方注释内容:

Transitions to TERMINATED state if either (SHUTDOWN and pool and queue empty) or (STOP and pool empty). If otherwise eligible to terminate but workerCount is nonzero, interrupts an idle worker to ensure that shutdown signals propagate. This method must be called following any action that might make termination possible – reducing worker count or removing tasks from the queue during shutdown. The method is non-private to allow access from ScheduledThreadPoolExecutor.

通过官方注释可知,只有在以下两种状态下,线程池能够被终止:

  • 线程池状态为 SHUTDOWN,Worker 数量为 0,任务阻塞队列为空
  • 线程池状态为 STOP,Worker 数量为 0

同时,在所有可能导致线程池终止的操作中都应该调用 tryTerminate() 方法来尝试终止线程池,因此线程池中 Worker 被删除时和任务阻塞队列中任务被删除时会调用 tryTerminate(),以达到在线程池符合终止条件时及时终止线程池。

相关文章:

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析

ThreadPoolExecutor 状态 ThreadPoolExecutor 继承了 AbstractExecutorService&#xff0c;并实现了 ExecutorService 接口&#xff0c;用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态&#xff0c;后 29 位表示 …...

vscode使用内置插件断点调试vue2项目

1、首先项目中要开启source-map 在vue.config.js 文件中 module.exports {configureWebpack: {devtool: process.env.NODE_ENV ! "production" ? "source-map" : ,} }2、项目根目录新建.vscode/launch.js文件 {"configurations": [{"ty…...

centos7 低版本docker 升级为高版本

删除 docker yum -y remove docker*安装 yum 管理工具 yum install -y yum-utils添加国内镜像 manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo查看可用软件版本yum list docker-ce --showduplicates|sort -r安装 yum -y install docke…...

了解SD-WAN与传统WAN的区别

近年来&#xff0c;许多企业选择了SD-WAN作为他们的网络解决方案。云基础架构的SD-WAN不仅具备成本效益&#xff0c;而且提供更安全、更可靠的WAN连接&#xff0c;有助于实现持续盈利。客户能够更好地控制他们的网络&#xff0c;个性化定制且无需额外成本。 那么&#xff0c;为…...

技术干货 | AI驱动工程仿真和设计创新

在当今快速发展的技术领域&#xff0c;人工智能&#xff08;AI&#xff09;、机器学习和深度学习等技术已经成为推动工程仿真和设计创新的关键力量。Altair技术经理张晨在Altair “AI FOR ENGINEERS”线下研讨会上发表了相关精彩演讲&#xff0c;本文摘自演讲内容&#xff0c;与…...

深度分析SQL与NoSQL数据库:优缺点、使用场景及选型指南

在大数据和云计算时代&#xff0c;数据库技术的发展日新月异。SQL&#xff08;关系型数据库&#xff09;和NoSQL&#xff08;非关系型数据库&#xff09;作为两大主流数据库技术&#xff0c;各有其独特的优势和使用场景。本文将深入分析SQL和NoSQL的定义、优缺点、使用场景&…...

Linux基础 - shell基础

目录 零. 简介 一、常见的 Shell 类型 二、Shell 命令格式 三、基本命令 四、通配符 五、重定向 六、管道 七、变量 八、条件判断和流程控制 零. 简介 Shell 是一种命令解释器&#xff0c;在 Ubuntu 系统中&#xff0c;它负责接收用户在命令行中输入的命令&#xff0c…...

一文搞懂Linux命令行下载OneDrive分享文件

一文搞懂Linux命令行下载OneDrive分享文件 什么问题&#xff1f; 因为OneDrive有些坑&#xff0c;无法从分享界面获取真实下载链接&#xff0c;比如下面这个链接&#xff1a; https://connecthkuhk-my.sharepoint.com/:f:/g/personal/jhyang13_connect_hku_hk/EsEgHtGOWbJIm…...

SpringBoot 实现RequestBodyAdvice封装统一接受类功能

一、相关往期文章 SpringBootVue实现AOP系统日志功能_aop的vue完整项目 Spring AOP (面向切面编程&#xff09;原理与代理模式—实例演示_面向切面aop原理详解 二、需求分析 按照一般情况&#xff0c;统一接受类可以像以下的方式进行处理&#xff1a; 如果不想使用 Request…...

贪吃蛇——c语言版

文章目录 演示效果实现的基本功能技术要点源代码实现功能GameStart打印欢迎界面和功能介绍绘制地图创建蛇创建食物 GameRun打印提示信息蛇每走一步 GameEnd蛇死亡后继续游戏 演示效果 贪吃蛇1.0演示视频 将终端应用程序改为控制台主机 实现的基本功能 贪吃蛇地图绘制蛇吃食物的…...

ctr/cvr预估之WideDeep模型

ctr/cvr预估之Wide&Deep模型 在探索点击率&#xff08;CTR&#xff09;和转化率&#xff08;CVR&#xff09;预估的领域中&#xff0c;我们始终追求的是一种既能捕获数据中的线性关系&#xff0c;又能发现复杂模式的模型。因子分解机&#xff08;Factorization Machines, …...

快速生成基于vue-element的后台管理框架,实现短时间二次开发

你是否遇到过当你想要独立开发一个项目时对反复造轮子的烦扰&#xff1f; 这种流水线的操作实在让人受不了 而vue-element-template很好的帮你解决了这个烦恼 只需克隆下来&#xff0c;改改图标&#xff0c;模块名&#xff0c;甚至样式&#xff0c;就会变成一个全新的自己的项目…...

PCIe 7.0 要来了,一文看懂PCIe发展和技术

PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;&#xff0c;即外围组件高速串行扩展总线标准&#xff0c;自其诞生以来&#xff0c;已成为计算机硬件中不可或缺的一部分。它以其高速串行通信能力和不断演进的技术规范&#xff0c;满足了日益增长的数据…...

API-事件类型

学习目标&#xff1a; 掌握事件类型 学习内容&#xff1a; 事件类型鼠标事件焦点事件键盘事件文本事件focus选择器案例 事件类型&#xff1a; 鼠标事件&#xff1a; <title>事件类型-鼠标事件</title><style>div {width: 200px;height: 200px;background-c…...

解决poweroff时需要等待其他服务关闭问题

当我们在执行poweroff或者reboot时会出现某个服务需要等待才能关闭系统,这个时候就可以在服务中添加如下: After=shutdown.target Conflicts=reboot.target halt.target poweroff.target Before=shutdown.target reboot.target halt.target poweroff.target具体实例: [Uni…...

ThinkPHP-导入Excel表格(通用版)

一、版本说明 1.PHP8.2、MySQL8.0、ThinkPHP8.0 2.使用前安装phpspreadsheet composer require phpoffice/phpspreadsheet 二、技术说明 因本人采用前后端分离&#xff0c;因此上传文件以及导入表格为分离开发&#xff0c;如无需分离开发则自行合并开发即可。 1.第一步&a…...

毕昇jdk教程

毕昇jdk教程指南链接&#xff1a;Wiki - Gitee.com...

【R语言】地理探测器模拟及分析(Geographical detector)

地理探测器模拟及分析 1. 写在前面2. R语言实现2.1 数据导入2.2 确定数据离散化的最优方法与最优分类2.3 分异及因子探测器&#xff08;factor detector&#xff09;2.4 生态探测器&#xff08;ecological detector&#xff09;2.5 交互因子探测器&#xff08;interaction dete…...

深入理解Qt属性系统[Q_PROPERTY]

Qt 属性系统是 Qt 框架中一个非常核心和强大的部分&#xff0c;它提供了一种标准化的方法来访问对象的属性。这一系统不仅使得开发者能够以一致的方式处理各种数据类型&#xff0c;还为动态属性的管理提供了支持&#xff0c;并与 Qt 的元对象系统紧密集成。在这篇文章中&#x…...

【C语言课程设计】员工信息管理系统

员工信息管理系统 在日常的企业管理中&#xff0c;员工信息的管理显得尤为重要。为了提高员工信息管理的效率&#xff0c;我们设计并实现了一个简单的员工信息管理系统。该系统主要使用C语言编写&#xff0c;具备输入、显示、查询、更新&#xff08;增加、删除、修改&#xff…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...