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

Coroutine 基础三 —— 结构化并发(二)

1、协程的结构化异常管理

如果一个协程抛异常,它所在的整个协程树上的其他协程(向上是父协程到根协程,向下是所有后代协程)都会被取消。因此协程发生异常的后果是十分严重的。

先讲原理,再说解决方案。

协程异常的处理与取消采用的是同一套逻辑。只不过取消协程抛出的是一个特殊异常 CancellationException,并进行特殊处理以取消协程。而协程异常是抛出其他普通的异常,

先查看抛出异常前后父子协程的状态:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)var childJob: Job? = nullval parentJob = scope.launch {childJob = launch {println("Child started")delay(3000)println("Child finished")}delay(1000)throw IllegalStateException("Wrong User!")}// 抛异常之前打印两个协程的状态delay(500)println("isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive}")println("isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled}")// 抛异常之后打印两个协程的状态delay(1500)println("isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive}")println("isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled}")delay(10000)
}

运行结果:

Child started
isActive: parent - true, child - true
isCancelled: parent - false, child - false
Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: Exception while trying to handle coroutine exceptionat kotlinx.coroutines.CoroutineExceptionHandlerKt.handlerException(CoroutineExceptionHandler.kt:33)at 
...
isActive: parent - false, child - false
isCancelled: parent - true, child - true

显而易见,在父协程抛出异常后,父协程被取消连带子协程一起被取消了。

注意,应用程序发生未被捕获的异常导致程序崩溃是 Android 的规则,而不是普通 JVM 的规则。JVM 中发生异常只会导致线程崩溃,而不会导致整个应用崩溃。因此上面的运行结果在崩溃后输出的父子协程状态数据也是正确的、可参考的。

假如让父协程抛出 CancellationException 代替 IllegalStateException,你会发现运行结果除了没有异常信息外,其余结果是一样的:

Child started
isActive: parent - true, child - true
isCancelled: parent - false, child - false
isActive: parent - false, child - false
isCancelled: parent - true, child - trueProcess finished with exit code 0

这是因为,协程的取消与异常处理,在底层走的是同一套流程,只不过取消处理相对于较为完整的异常处理,过程有所简化。

二者的第一个不同是,取消的作用方向是单向的,只会取消自己与所有下层协程;而异常的作用方向是双向的,除了取消自己,也会向上取消所有上层协程,向下取消所有下层协程。

源码证据是在 Job 接口的实现类 JobSupport 中,该类作为所有 Job 的父类被继承。当取消子协程时:

    public open fun childCancelled(cause: Throwable): Boolean {if (cause is CancellationException) return truereturn cancelImpl(cause) && handlesException}

如果取消的原因是 CancellationException 就返回 true 不继续执行后续的 cancelImpl(),该函数内部正是处理取消父协程的代码。

为什么会有这种区别?这实际上体现了协程的设计思想。

两个协程之所以被写成父子协程的关系,是因为二者在逻辑上有相互包含的关系(当然,从运行角度,二者是并行关系),子协程执行的内容通常是父协程任务的子流程。

从正常的逻辑上讲,当一个大流程被取消时,它内部的子流程也就没用了。因此,才会给协程设计这种取消时连带的性质。类似的,父协程等待所有子协程完成后再结束,也是因为所有子流程完成后整个大流程才算完成,所以才有父协程等待子协程的性质。

子协程取消,对外部大流程而言可能只是一个正常的事件而已,因此子协程的取消不会导致父协程的取消。

但如果子协程抛异常了,通常意味着父协程被损坏,影响整个大流程。因此子协程抛异常,会导致父协程也以该异常为原因而取消。

二者的第二个不同,取消有两种方式,可以在协程内部抛 CancellationException,也可以在协程外部调用 cancel();而异常流程的触发只有一种方式,就是在协程内抛异常。

当然,协程源码实际上也存在通过 cancel() 触发异常流程的函数:

    @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")public fun cancel(cause: Throwable? = null): Boolean

但是明显能看到该函数是 HIDDEN 的,不提供给开发者使用。实际上也很好理解,取消函数就触发取消流程,不要触发异常流程搞得逻辑混乱。

二者的第三个不同,用于取消的 CancellationException 除了用来处理取消协程之外就别无用处;而异常流程里抛出的异常对象除了用来取消协程,还会把该异常对象暴露给线程。直接体现就是本节举例的,协程抛出 IllegalStateException 导致线程崩溃输出 log 信息。

2、CoroutineExceptionHandler

先从一个例子看起:

// 示例代码 1
fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {try {throw RuntimeException()} catch (e: Exception) {}}delay(10000)
}

在协程内部将有可能抛出异常的代码用 try-catch 包起来,可以捕获异常。但倘若将 try-catch 挪到协程 launch 之外:

// 示例代码 2
fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)try {scope.launch {throw RuntimeException()}} catch (e: Exception) {}delay(10000)
}

这个异常就无法被捕获。这种形式类似于:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)try {thread {throw RuntimeException()}} catch (e: Exception) {}delay(10000)
}

子线程内抛出的异常,在主线程去捕获,肯定是捕获不到的。对于协程也是一样的,像示例代码 2 那样,try-catch 只能捕获到协程启动阶段可能抛出的异常,也就是 scope.launch 如果抛异常了,在它外面的 try-catch 可以捕获到。但对于协程运行阶段的代码,也就是 launch 大括号里面的代码,它是运行在 Default 线程池的,与 try-catch 都不在一个线程中,因此无法被捕获。想要捕获协程运行阶段的代码,可以像示例代码 1 那样把 try-catch 放在协程内部。

那么真的就没办法在协程外部捕获协程内部的异常了吗?答案是有,可以使用 CoroutineExceptionHandler,将其对象传给最外层协程

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught $exception")}// 这个 try-catch 无法捕获到 launch 内的异常try {scope.launch(handler) {throw RuntimeException()}} catch (e: Exception) {}delay(10000)
}

运行输出结果,异常没有抛出:

Caught java.lang.RuntimeException

使用 CoroutineExceptionHandler 一定要注意的是,CoroutineExceptionHandler 对象只能设置给最外层协程,这样最外层协程本身及其所有子协程所抛出的异常才可被捕获。如果设置给内层的某个子协程,即便是该子协程抛出异常也无法被捕获:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught $exception")}scope.launch(/*handler*/) {// 这样还是会抛异常launch(handler) {throw RuntimeException()}}delay(10000)
}

3、异常结构化管理本质

3.1 UncaughtExceptionHandler

捕获线程异常,除了 try-catch,还可以使用 UncaughtExceptionHandler:

fun main() = runBlocking<Unit> {val thread = Thread {throw RuntimeException("Thread error!")}// 在线程启动之前设置 UncaughtExceptionHandlerthread.setUncaughtExceptionHandler { _, e -> println("Caught $e") }thread.start()
}

在线程启动之前,可以调用 setUncaughtExceptionHandler() 给该线程设置异常捕获,运行结果如下:

Caught java.lang.RuntimeException: Thread error!

除了给单个线程设置,也可以给所有线程设置默认的 UncaughtExceptionHandler:

fun main() = runBlocking<Unit> {Thread.setDefaultUncaughtExceptionHandler { _, e -> println("Default caught: $e") }val thread = Thread {throw RuntimeException("Thread error!")}thread.setUncaughtExceptionHandler { _, e -> println("Caught $e") }thread.start()thread {throw IllegalStateException("Wrong User!")}
}

运行结果:

Caught java.lang.RuntimeException: Thread error!
Default caught: java.lang.IllegalStateException: Wrong User!

结果显示,对于单独设置了 UncaughtExceptionHandler 的线程,会使用单独设置的 UncaughtExceptionHandler 捕获异常。未设置的线程会使用默认的 UncaughtExceptionHandler 捕获异常。

对于异常处理要有一些深入的思考。比如对于 Android 应用而言,发生未捕获的异常会发生 FC 导致应用崩溃关闭。为了避免 FC 的发生,在写代码的时候,对于可能发生异常的代码,我们会用 try-catch 把它包起来,在 catch 代码块中做一些补救工作,比如尝试解决问题(函数重新执行或者重置变量值等等)或重启应用 Todo。而不是单单只为了通过 catch 吞掉这个异常而不发生 FC,因为即便不 FC,抛异常也意味着你的应用没有正常工作。盲目的吞掉异常让软件在异常状态下继续运行,可能会产生无法预料的结果。

对于能提前预料的异常,可以使用 try-catch。但是往往会有没有预判的异常出现,跑出 catch 的捕获,最终被 UncaughtExceptionHandler 接收到。此时线程已经运行结束了,没有机会做补救工作了,只能做一些善后工作,使用通用的解决方案优雅地结束应用。一般有两步:收尾工作(如记录崩溃日志)以及重启或杀死应用:

Thread.setDefaultUncaughtExceptionHandler { _, e ->// 记录崩溃日志println("Default caught: $e")// 结束或重启应用exitProcess(1)
}

对于为单个线程设置的 UncaughtExceptionHandler,如果该线程只是在内部完成自己的工作,其崩溃不影响其他线程,那么可以在 UncaughtExceptionHandler 中尝试重启线程。当然,一个线程独立完成一件事的场景比较少见。

3.2 CoroutineExceptionHandler

让我们再回头看协程,如果没有捕获协程的异常,它最终会抛到线程的环境中,通过默认的 UncaughtExceptionHandler 可以捕获协程抛出的异常:

fun main() = runBlocking<Unit> {// 协程异常会被线程的 DefaultUncaughtExceptionHandler 捕获Thread.setDefaultUncaughtExceptionHandler { _, e ->println("Caught in DefaultUncaughtExceptionHandler: $e")}val scope = CoroutineScope(EmptyCoroutineContext)// 协程没有捕获异常val job = scope.launch {launch {throw RuntimeException()}}job.join()
}

使用 CoroutineExceptionHandler 为 job 这个协程树添加异常捕获处理,可以实现类似于为单个线程设置 UncaughtExceptionHandler 的效果:

fun main() = runBlocking<Unit> {Thread.setDefaultUncaughtExceptionHandler { _, e ->println("Caught in DefaultUncaughtExceptionHandler: $e")}val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught in CoroutineExceptionHandler: $exception")}val job = scope.launch(handler) {launch {throw RuntimeException()}}job.join()
}

异常会被 CoroutineExceptionHandler 捕获:

Caught in CoroutineExceptionHandler: java.lang.RuntimeException

上一节我们说,很多任务都是通过多个线程完成的,因此在单个线程的 UncaughtExceptionHandler 做重启线程的适用场景并不多。但是我们看 CoroutineExceptionHandler 正是为一个协程树做异常善后工作所用的,将一个任务放在协程树中去执行,当某一个协程发生异常后,可以在 CoroutineExceptionHandler 中重启整个协程树,这就具有更广泛的实际意义了。

当然,CoroutineExceptionHandler 并不能替代 DefaultUncaughtExceptionHandler 去对整个应用做未知异常的善后工作,它只能针对一棵协程树。即便是纯协程应用,也是要通过 DefaultUncaughtExceptionHandler 来做通用拦截。

现在再来考虑,子协程的异常为什么要交到最外层父协程那里去注册 CoroutineExceptionHandler?因为注册 CoroutineExceptionHandler 的目的是善后,不管是哪个协程发生了异常,都是对整个协程树进行善后。因此设置给最外层协程最方便。

协程异常的结构化管理的本质,是针对协程发生的未知异常善后方案。因为已知异常,直接通过在协程内部 try-catch 就可以修复,只有未知异常才会走到结构化异常处理流程。

4、async 的异常处理

async 的异常处理与 launch 的大致相同,但是因为 async 启动的协程,往往需要通过 await() 获取结果,会有两点比较明显的差异:

  1. 如果在 async 内发生异常,那么调用 await() 的协程会受到双重影响
  2. async 启动的协程在 await() 抛出异常,这个异常往往在协程内部就被 try-catch 捕获了。因此 async 作为最外层父协程存在时,不会向内部异常抛出到线程世界,给最外层的 async 设置 CoroutineExceptionHandler 也就没有作用了

先来看第一点,示例代码:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught in CoroutineExceptionHandler: $exception")}val job = scope.launch(handler) {val deferred = async {delay(1000)throw RuntimeException()}launch {// await 也会抛出 async 内抛出的 RuntimeExceptiontry {deferred.await()} catch (e: Exception) {println("Caught in await: $e")}// delay 用来验证 async 抛出的异常触发了结构化取消,导致// async -> job -> 当前协程被取消try {delay(1000)} catch (e: Exception) {println("Caught in delay: $e")}}}job.join()
}

运行结果:

Caught in await: java.lang.RuntimeException
Caught in delay: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=StandaloneCoroutine{Cancelling}@6463030a
Caught in CoroutineExceptionHandler: java.lang.RuntimeException

观察结果:

  • 因为 await 也会抛出 async 相同的异常,所以它的 try-catch 先捕获到异常
  • 然后由于 async 抛的异常先让自己,也就是 deferred 被取消,进而导致父协程 job 被取消,进一步导致 delay 所在的协程也要被取消,所以 delay 抛出 CancellationException
  • async 与 await 抛出的异常最终会被最外层协程的 CoroutineExceptionHandler 捕获

以上过程能看出,调用 await 的协程实际上受到双重影响:一是 await 会抛出与 async 同样的异常导致该协程进入异常处理流程;二是 async 抛出异常使得父协程与兄弟协程都会被取消,await 所在协程还会触发取消流程

再看第二点差异,示例代码:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught in CoroutineExceptionHandler: $exception")}scope.async {val deferred = async {delay(1000)throw RuntimeException("Error!")}launch(Job()) {try {deferred.await()} catch (e: Exception) {println("Caught in await: $e")}}}delay(3000)
}

由于 await 抛异常会被 try-catch 捕获,因此 async 就不会向线程世界抛出异常。但倘若没有为 await 添加 try-catch,其异常还是会被最外层的 async 抛到线程世界,需要 CoroutineExceptionHandler:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val handler = CoroutineExceptionHandler { _, exception ->println("Caught in CoroutineExceptionHandler: $exception")}scope.async(handler) {val deferred = async {delay(1000)throw RuntimeException("Error!")}launch(Job()) {deferred.await()}}delay(3000)
}

运行结果:

Caught in Coroutine: java.lang.RuntimeException: Error!

如果不设置 handler 给最外层 async,程序就会红字异常信息。所以我才觉得这里它讲的不对,它的意思是说 async 就用不到 CoroutineExceptionHandler,因为 async 不会向线程世界抛异常。但实际测试结果表明,async 不向线程世界抛异常,是因为在协程内对 await 用了 try-catch 把异常捕获了。如果没有捕获,异常还是需要通过最外层的 CoroutineExceptionHandler 来捕获处理。

5、SupervisorJob

SupervisorJob

@Suppress("FunctionName")
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {// 子协程被取消,父协程会调用 childCancelledoverride fun childCancelled(cause: Throwable): Boolean = false
}

子协程被取消,父协程会调用 childCancelled。但对于 SupervisorJob 而言,它直接返回 false 表示子协程发生一般异常(非 CancellationException)时,不取消父协程。也就是说,SupervisorJob 不会因为自己的子协程发生普通异常而被取消,只有在子协程被取消而发生 CancellationException 时才会被取消。

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val supervisorJob = SupervisorJob()scope.launch(supervisorJob) {throw RuntimeException("Error!")}delay(100)println("Parent job cancelled: ${supervisorJob.isCancelled}")delay(1000)
}

在抛出异常后仍然会输出:

Parent job cancelled: false

说明子协程的异常没有导致 supervisorJob 被取消。

相关文章:

Coroutine 基础三 —— 结构化并发(二)

1、协程的结构化异常管理 如果一个协程抛异常&#xff0c;它所在的整个协程树上的其他协程&#xff08;向上是父协程到根协程&#xff0c;向下是所有后代协程&#xff09;都会被取消。因此协程发生异常的后果是十分严重的。 先讲原理&#xff0c;再说解决方案。 协程异常的处…...

GXUOJ-算法-第一次作业

1.整数划分 问题描述 GXUOJ | 整数划分 题解 #include<bits/stdc.h> using namespace std; const int N1010,mod1e97;int n; int f[N];int main(){cin>>n;f[0]1;for(int i1;i<n;i){for(int ji;j<n;j){f[j](f[j]f[j-i])%mod;}}cout<<f[n]; } 2.汉诺塔…...

Springboot项目Druid运行时动态连接多数据源的功能

项目支持多数据库连接是个很常见的需求&#xff0c;这不仅是要在编译前连已经知道的多个数据库&#xff0c;有时还要在程序运行时连后期增加的多个数据源来获得数据。 一、编译前注册数据库连接 1.引入依赖包 <!-- springboot 3.x --><dependency><groupId&g…...

字符串匹配——KMP算法

前言 刷到字符串匹配的力扣题了【28. 实现 strStr() 】&#xff0c;这题简单吧用库函数做就可以&#xff0c;说难吧&#xff0c;就得引出大名鼎鼎的线性匹配算法——KMP。 目录 KMP 算法背景与原理算法优势 前缀表1. 构建Next数组2. 搜索匹配 KMP 算法背景与原理 KMP&#x…...

Qt开发技术【下拉复选框 MultiSelectComboBox 自定义全选项】

继承ComboBox完成下拉复选框 自定义全选项 效果图 整个控件继承于QCombobox类。主要修改QLineEdit、QListWidget这两部分&#xff0c;QComboBox提供如下接口&#xff0c;可以将这两部分设置为新建的QLineEdit、QListWidget对象 CMultiSelectComboBox::CMultiSelectComboBo…...

20_HTML5 SSE --[HTML5 API 学习之旅]

HTML5 Server-Sent Events (SSE) 是一种技术&#xff0c;它允许服务器向浏览器推送更新。与传统的轮询不同&#xff0c;SSE提供了真正的单向实时通信通道&#xff1a;服务器可以主动发送数据到客户端&#xff0c;而不需要客户端发起请求。这对于实现实时更新的应用非常有用&…...

jetson Orin nx + yolov8 TensorRT 加速量化 环境配置

参考【Jetson】Jetson Orin NX纯系统配置环境-CSDN博客 一 系统环境配置&#xff1a; 1.更换源&#xff1a; sudo vi /etc/apt/sources.list.d/nvidia-l4t-apt-source.list2.更新源&#xff1a; sudo apt upgradesudo apt updatesudo apt dist-upgrade sudo apt-get updat…...

Android Studio IDE环境配置

​需要安装哪些东西&#xff1a; Java jdk Java Downloads | OracleAndroid Studio 下载 Android Studio 和应用工具 - Android 开发者 | Android DevelopersAndroid Sdk 现在的Android Studio版本安装时会自动安装&#xff0c;需要注意下安装的路径Android Studio插件…...

PTA 7-2 0/1背包问题(回溯法) 作者 王东 单位 贵州师范学院

0/1背包问题。给定一载重量为W的背包及n个重量为wi、价值为vi的物体&#xff0c;1≤i≤n,要求重量和恰好为W具有最大的价值。 输入格式: 第一行输入背包载重量W及背包个数n&#xff0c;再依次输入n行&#xff0c;每行为背包重量wi和价值vi。 输出格式: 第一行输出装入背包内…...

Matlab环形柱状图

数据准备&#xff1a; 名称 数值 Aa 21 Bb 23 Cc 35 Dd 47 保存为Excel文件后&#xff1a; % Load data from Excel file filename data.xlsx; % Ensure the file is in the current folder or provide full path dataTable readtable(filena…...

【AI大模型】探索GPT模型的奥秘:引领自然语言处理的新纪元

目录 &#x1f354; GPT介绍 &#x1f354; GPT的架构 &#x1f354; GPT训练过程 3.1 无监督的预训练语言模型 3.2 有监督的下游任务fine-tunning &#x1f354; 小结 学习目标 了解什么是GPT.掌握GPT的架构.掌握GPT的预训练任务. &#x1f354; GPT介绍 GPT是OpenAI公…...

5.Python爬虫相关

爬虫 爬虫原理 爬虫&#xff0c;又称网络爬虫&#xff0c;是一种自动获取网页内容的程序。它模拟人类浏览网页的行为&#xff0c;发送HTTP请求&#xff0c;获取网页源代码&#xff0c;再通过解析、提取等技术手段&#xff0c;获取所需数据。 HTTP请求与响应过程 爬虫首先向…...

Windows系统上配置eNSP环境的详细步骤

华为eNSP&#xff08;Enterprise Network Simulation Platform&#xff09;是一款针对华为数通网络设备的网络仿真平台&#xff0c;用于辅助工程师进行网络技术学习、方案验证和故障排查等工作。以下是在Windows系统上配置eNSP环境的详细步骤&#xff1a; 1. 准备工作 下载安…...

Database.NET——一款轻量级多数据库客户端工具

文章目录 Database.NET简介下载使用使用场景总结 Database.NET简介 Database.NET 是一个功能强大且易于使用的数据库管理工具&#xff0c;适用于多种数据库系统。它为开发者和数据库管理员提供了一个统一的界面&#xff0c;可以方便地管理和操作不同类型的数据库。 支持的数据…...

新浪微博C++面试题及参考答案

多态是什么&#xff1f;请详细解释其实现原理&#xff0c;例如通过虚函数表实现。 多态是面向对象编程中的一个重要概念&#xff0c;它允许不同的对象对同一消息或函数调用做出不同的响应&#xff0c;使得程序具有更好的可扩展性和灵活性。 在 C 中&#xff0c;多态主要通过虚函…...

计算机视觉目标检测-1

文章目录 摘要Abstract1.目标检测任务描述1.1 目标检测分类算法1.2 目标定位的简单实现思路1.2.1 回归位置 2.R-CNN2.1 目标检测-Overfeat模型2.1.1 滑动窗口 2.2 目标检测-RCNN模型2.2.1 非极大抑制&#xff08;NMS&#xff09; 2.3 目标检测评价指标 3.SPPNet3.1 spatial pyr…...

【物联网技术与应用】实验15:电位器传感器实验

实验15 电位器传感器实验 【实验介绍】 电位器可以帮助控制Arduino板上的LED闪烁的时间间隔。 【实验组件】 ● Arduino Uno主板* 1 ● 电位器模块* 1 ● USB电缆*1 ● 面包板* 1 ● 9V方型电池* 1 ● 跳线若干 【实验原理】 模拟电位器是模拟电子元件&#xff0c;模…...

java常用类(上)

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;javase &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一、包装类 1.1包装类…...

包管理工具npm、yarn、pnpm、cnpm详解

1. 包管理工具 1.1 npm # 安装 $ node 自带 npm# 基本用法 npm install package # 安装包 npm install # 安装所有依赖 npm install -g package # 全局安装 npm uninstall package # 卸载包 npm update package # 更新包 npm run script #…...

CI/CD是什么?

CI/CD 定义 CI/CD 代表持续集成和持续部署&#xff08;或持续交付&#xff09;。它是一套实践和工具&#xff0c;旨在通过自动化构建、测试和部署来改进软件开发流程&#xff0c;使您能够更快、更可靠地交付代码更改。 持续集成 (CI)&#xff1a;在共享存储库中自动构建、测试…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

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

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

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...