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

Kotlin 协程基础十 —— 协作、互斥锁与共享变量

Kotlin 协程基础系列:

Kotlin 协程基础一 —— 总体知识概述

Kotlin 协程基础二 —— 结构化并发(一)

Kotlin 协程基础三 —— 结构化并发(二)

Kotlin 协程基础四 —— CoroutineScope 与 CoroutineContext

Kotlin 协程基础五 —— Channel

Kotlin 协程基础六 —— Flow

Kotlin 协程基础七 —— Flow 操作符(一)

Kotlin 协程基础八 —— Flow 操作符(二)

Kotlin 协程基础九 —— SharedFlow 与 StateFlow

Kotlin 协程基础十 —— 协作、互斥锁与共享变量

本节将介绍在协程间如果有先后执行、互相等待的需求时,应该怎样去处理这种等待和协作的工作。更会与 Java 的线程的协作工作对比,从而说明,在线程中通常不太简单的协作操作,在协程中很容易实现。

1、协程间的协作与等待

从运行角度来看,协程天生就是并行的,不论是对同等级的协程、父子协程还是毫无关系的协程。假如我们需要让协程互相等待,希望在协程的执行过程中可以停住,等待别的协程执行完毕,可以使用 Job 的 join() 或 Deferred 的 await()。

线程对于这种互相等待的需求可以通过 Thread 的 join(),还有 Future 和 CompletableFuture 以及 CountDownLatch。

CountDownLatch 适用于一个线程等待多个线程:

fun main() = runBlocking<Unit> {// countdown 译为倒计时,latch 是门闩、插销,组合起来就是用于倒计时的插销val countDownLatch = CountDownLatch(2)thread {// await() 会在 CountDownLatch 内的 count 减到 0 时结束等待countDownLatch.await()println("Count in CountDownLatch is 0 now,I'm free!")}thread {sleep(1000)// countDown() 会调用原子操作让 CountDownLatch 内的 count 减 1countDownLatch.countDown()println("Invoke countDown,count: ${countDownLatch.count}")}thread {sleep(2000)countDownLatch.countDown()println("Invoke countDown,count: ${countDownLatch.count}")}
}

运行结果:

Invoke countDown,count: 1
Count in CountDownLatch is 0 now,I'm free!
Invoke countDown,count: 0

修改 CountDownLatch 构造方法的 count 参数就可以修改要等待的线程数量,对于这种一个等待多个的业务需求,在协程中也可以用 join() 来做:

fun main() = runBlocking<Unit> {// 两个前置任务val preJob1 = launch {delay(1000)}val preJob2 = launch {delay(2000)}// 此协程需要等待两个协程执行之后再运行自己的内容launch {preJob1.join()preJob2.join()// 等待完前置任务,再做自己的事...}
}

实际上线程里也可以这么做,只不过因为线程本身的结构化管理比较麻烦,所以在正式的项目里很少真正的这么写。但因为协程可以结构化取消,因此它的 join() 比线程的 join() 更实用,在正式项目里的应用也较多。

其实,用 Channel 也能实现类似 CountDownLatch 那种,不指定具体等待哪些协程,只等待固定的次数的效果:

private fun channelSample() = runBlocking<Unit> {// 指定 Channel 的容量为 2val channel = Channel<Unit>(2)// 由于要等待两次发送数据才能继续执行后续代码,因此要 repeat(2) 接收launch {repeat(2) {channel.receive()}}launch {delay(1000)channel.send(Unit)}launch {delay(2000)channel.send(Unit)}
}

通过两个简单的例子可以发现,线程中有些复杂、比较底层、不太容易使用的协作和等待 API,在协程中的对应/等价 API 难度要大大降低。

2、select():先到先得

select() 会在内部开启多线竞赛,谁最快就用谁。

onJoin() 是仅限于在 select 代码块中才能调用的函数,它是一个监听注册,会监听 Job 的结束,不论 Job 是正常结束还是被取消,在其结束时都会回调执行 onJoin() 大括号的内容,并且大括号的返回值会作为 select() 的返回值:

fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val job1 = scope.launch {delay(1000)println("job1 done")}val job2 = scope.launch {delay(2000)println("job2 done")}val job = scope.launch {val result = select {// select 只执行最先结束的 onJoin 回调job1.onJoin {1}job2.onJoin {2}}println("result: $result")}joinAll(job, job1, job2)
}

运行结果:

job1 done
result: 1
job2 done

结果能看出,select() 只执行了最先结束的 job1 的 onJoin,没有执行 job2 的。

与 Job 的 onJoin() 功能类似的还有:Deferred 的 onAwait()、Channel 的 onSend()、onReceive() 以及 onReceiveCatching()。此外还有一个特殊的函数 onTimeout(),如果 select() 内所有的监听回调都没有在 onTimeout() 设置的超时时间内完成,那么就由 onTimeout() 作为 select() 的返回值:

@OptIn(ExperimentalCoroutinesApi::class)
fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val job1 = scope.launch {delay(1000)println("job1 done")}val deferred = scope.async {delay(2000)println("deferred done")}val channel = Channel<String>()val job = scope.launch {val result = select {// select 只执行最先结束的 onJoin 回调job1.onJoin {1}deferred.onAwait {2}channel.onSend("haha") {}/*channel.onReceive {}channel.onReceiveCatching {}*/onTimeout(500) {"Timeout!"}}println("result: $result")}joinAll(job, job1)
}

运行结果:

result: Timeout!
job1 done

3、互斥锁和共享变量

在遇到一个不太好理解的知识点时,我们还是先说线程,再引入到协程中。

线程中有一个术语叫竞态条件,或者说竞争条件,英文是 race condition。这个词的含义比较广,在 Java 和 Kotlin 这种高级编程语言中,它指的是多个线程访问共享资源时,由于缺乏并发控制,导致资源的访问顺序不受控,进而导致出现错误的结果的条件。

在 Kotlin 中,仍然可以使用我们在 Java 中熟知的 synchronized 和 Lock 这两种锁机制来保证共享资源的线程安全,也提供了新的选项,下面我们来说一说。

3.1 @Synchronized

Kotlin 中没有 synchronized 关键字,代替它的是 @Synchronized 注解。对于方法而言,使用 @Synchronized 注解的作用与 Java 中使用 synchronized 关键字修饰方法的作用是一样的。被 @Synchronized 注解标记的方法不能同时被多个线程(注意,不是协程)执行。

而 Java 中 synchronized 代码块在 Kotlin 中被 synchronized 函数代替了:

fun main() = runBlocking<Unit> {var number = 0val lock = Any()val thread1 = thread {repeat(100_000) {synchronized(lock) {number++}}}val thread2 = thread {repeat(100_000) {synchronized(lock) {number--}}}thread1.join()thread2.join()println("result: $number") // 输出 0
}

同样的代码结构也可以用在协程中:

fun main() = runBlocking<Unit> {var number = 0val lock = Any()val scope = CoroutineScope(EmptyCoroutineContext)val job1 = scope.launch {repeat(100_000) {synchronized(lock) {number++}}}val job2 = scope.launch {repeat(100_000) {synchronized(lock) {number--}}}job1.join()job2.join()println("result: $number")
}

synchronized() 仍然掐住的是线程,确切的说是掐住了执行 synchronized() 所在的协程的线程。虽然这样做有点浪费,因为不止掐住了协程,连运行该协程代码的线程都被掐住了,但确实实现了共享资源的线程安全,而且 synchronized() 本来也是针对线程的,只不过从协程的角度看,如果可以只掐住协程,不影响运行该协程代码的线程就更好了。

这个区别就好像 delay() 与 sleep() 一样。协程的 delay() 只会挂起当前的协程,但是不会影响其所在的线程;而 sleep() 是让整个线程休眠。因此在协程中,为了不影响整个线程,我们通常都是使用 delay() 仅作用于当前协程,而不会使用 sleep() 为了让协程挂起而影响到整个线程的运行。下一节要讲的 Mutex 就可以解决这个问题。

Lock 的用法也大致相同,这里不多赘述。

3.2 Mutex

Mutex 是计算机领域的专属词汇,全称是 mutual exclusion,即互斥。Kotlin 提供的 Mutex 是基于协程的、挂起式的,不同于前面两个是基于线程的、阻塞式的。Mutex 是协程自己的实现,它不卡线程,性能更好,使用也很方便:

fun main() = runBlocking<Unit> {var number = 0val mutex = Mutex()val scope = CoroutineScope(EmptyCoroutineContext)val job1 = scope.launch {repeat(100_000) {try {mutex.lock()number++} finally {mutex.unlock()}}}val job2 = scope.launch {repeat(100_000) {mutex.withLock {number--}}}job1.join()job2.join()println("result: $number")
}

job1 内使用的是常规用法,在操作共享变量前用 lock() 加锁,在 finally 代码块中解锁。job2 内使用的是简便写法,withLock() 将代码块内的代码放入 try 中执行,在 finally 中用 unlock() 解锁:

@OptIn(ExperimentalContracts::class)
public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T {contract {callsInPlace(action, InvocationKind.EXACTLY_ONCE)}lock(owner)return try {action()} finally {unlock(owner)}
}

Mutex 的优势是性能,但由于它是基于协程的,因此只能在协程中使用。所以,如果只在协程中使用共享资源,那么就用 Mutex,如果需要在线程中使用,就要用上一节说的 synchronized 与 Lock。

3.3 Semaphore

Java 还有一个 Semaphore,信号量,一个可以被多个线程持有的锁。你可以在它的构造方法中指定它最多可以被几个线程持有,如果有多余指定数量的线程去获取 Semaphore 就会陷入等待。获取锁用 acquire(),释放锁用 release()。

由于共享变量是只要有两个线程同时访问就会导致出错了,因此允许多个线程持有的 Semaphore 并不能用于解决竞态条件的问题,它是用来做性能控制的。你可以用它来实现类似线程池的功能,只不过你实现出来的是自己定制的对象池:同一时间最多只有多少个对象同时在做事,满了之后如果再来新对象就得等着,直到有新的坑让出来,这些新对象才能开始做事。

Kotlin 提供了一个 Semaphore 的协程版本,就叫 Semaphore,定位与 Java 的 Semaphore 相同,只不过是协程版本。

3.4 其他 API

在传统的线程系统里,还有一组典型的 API:wait()、notify()、notifyAll()。它们三个属于更底层的 API,在线程系统里,它既能实现互斥锁,也能实现线程之间相互等待的功能。但事实上,这些年已经基本没人再用这组函数了。因为 synchronized 关键字与 Lock 的推出,已经基本上完全替代了它们,而且它们用起来也很麻烦,所以现在没人用。正因如此,协程没有推出与它们类似的 API。

AtomicInteger 与 CopyOnWriteArrayList 等等也可以在协程中使用。虽然它们是针对线程的,但是卡住线程的同时一定把协程也卡住了。所以在协程里也可以无风险地使用。

此外,volatile 与 transient 也可以在协程中使用,只不过不再是关键字,而是注解。

4、ThreadLocal

ThreadLocal 是线程的局部变量,即该变量在每个线程都是独立的,从不同的线程中访问该变量,这些线程对变量的值的读写都是相互独立的,对每个线程都有独立的副本。

ThreadLocal 是用来干嘛的?它的定位就像它的名字一样,就是针对线程的局部变量。Java 变量按照作用域由小到大可以划分为局部变量(方法内)、成员变量(类内)、静态变量(全局),ThreadLocal 是一种介于局部变量和静态变量之间的一种变量,范围比方法大,比静态全局小,只在当前线程范围内有效。

ThreadLocal 是对 Java 线程一个很关键的能力补充。前面提过,协程相对线程的一大优势就是线程不具备结构化管理的能力,而协程结构化管理的能力相当强大。线程不具备结构化管理的能力,但我们开发时是有结构化管理的需求的,这时就要用 ThreadLocal。有了 ThreadLocal 之后,在同一个线程里执行的多个方法之间就可以共享变量了,且该共享变量只针对当前线程有效,跨线程时还是独立的。因此 ThreadLocal 通常会作为静态变量存在。

ThreadLocal 在协程中的等价物是什么?有什么东西是跨方法的、针对协程的局部变量吗?CoroutineContext 就是协程里的 ThreadLocal。

本来,由于协程是具备结构化管理能力的,你完全不需要在协程内使用 ThreadLocal。但是开发过程中,免不了与 Java 代码进行协作,如果想在协程代码里访问老代码里的 ThreadLocal 对象,是不能像如下这样直接使用的:

val kotlinLocalString = ThreadLocal<String>()
fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val job = scope.launch {kotlinLocalString.set("Test")delay(1000)println(kotlinLocalString.get())}job.join()
}

kotlinLocalString 的 get() 拿到的值一定是 set() 设置的值吗?不一定!因为虽然协程没变,但是执行协程代码的线程有可能改变了,delay() 的时候线程被让出,可能会去执行其他协程的代码。等 delay() 结束继续执行下面代码的时候,有可能就不是在刚才的线程中执行了。因为协程只能保证在执行挂起函数之后依然运行在刚才的 ContinuationInterceptor 所管理的某一个线程池上,不能保证同一个线程。

因此 ThreadLocal 不能在协程中直接使用,因为它的效果在协程中变得不可靠了。怎么办?用 asContextElement() 把 ThreadLocal 转换成 CoroutineContext:

val kotlinLocalString = ThreadLocal<String>()
fun main() = runBlocking<Unit> {val scope = CoroutineScope(EmptyCoroutineContext)val job = scope.launch {val stringContext = kotlinLocalString.asContextElement("Test")withContext(stringContext) {delay(1000)println(kotlinLocalString.get())}}job.join()
}

asContextElement() 是 ThreadLocal 的扩展函数,它会把参数里的值封装到返回值的 ThreadLocalElement 中。再将结果填到 withContext() 的参数中,包住获取 ThreadLocal 值的代码,这时候里面的 ThreadLocal 就是对协程兼容的了。不管里面怎么切协程,只要没出协程,它的值都会被保持住。

相关文章:

Kotlin 协程基础十 —— 协作、互斥锁与共享变量

Kotlin 协程基础系列&#xff1a; Kotlin 协程基础一 —— 总体知识概述 Kotlin 协程基础二 —— 结构化并发&#xff08;一&#xff09; Kotlin 协程基础三 —— 结构化并发&#xff08;二&#xff09; Kotlin 协程基础四 —— CoroutineScope 与 CoroutineContext Kotlin 协程…...

DAMA CDGA 备考笔记(二)

1. 考点分布 2. 第二章 数据处理伦理知识点总结 伦理是建立在是非观念上的行为准则。伦理准则通常侧重于公平、尊重、责任、诚信、质量、可靠性、透明度和信任等方面。数据伦理是一项社会责任问题不是法律问题。 度量指标&#xff1a;培训员工人数、合规/不合规事件、企业高管…...

【Lua学习之旅】之单行/多行注释

Lua的注释 单行注释多行注释 单行注释 lua中的单行注释采用两个短横线"--" --这是lua单行注释多行注释 写法一&#xff1a; --[[ 这个lua的多行注释&#xff0c; 很多资料说多行注释不可以嵌套&#xff0c; 根据我的测试&#xff0c;这种写法的多行注释在lua54版…...

【线性代数】行列式的概念

d e t ( A ) ∑ i 1 , i 2 , ⋯ , i n ( − 1 ) σ ( i 1 , ⋯ , i n ) a 1 , i 1 a 2 , i 2 , ⋯ , a n , i n det(A) \sum_{i_1,i_2,\cdots,i_n } (-1)^{\sigma(i_1,\cdots,i_n)} a_{1,i_1}a_{2,i_2},\cdots, a_{n,i_n} det(A)i1​,i2​,⋯,in​∑​(−1)σ(i1​,⋯,in​)a1…...

react中hooks之useEffect 用法总结

1. 什么是函数的副作用&#xff08;Side Effects&#xff09; 副作用是指在组件渲染过程中&#xff0c;除了返回 JSX 之外的其他操作&#xff0c;例如&#xff1a; 数据获取&#xff08;API 调用&#xff09;订阅数据源手动修改 DOM设置定时器存储数据日志记录 纯函数是特定的…...

小型、中型无人机执照学习和考试区别详解

小型、中型无人机执照的学习和考试在多个方面存在区别。以下是对两者的详细对比&#xff1a; 一、定义与适用范围 1. 小型无人机&#xff1a; 通常指起飞重量在7kg至25kg之间的无人机。 适用于多种应用场景&#xff0c;包括商业飞行、航拍、农业植保等。 必须持有民航局无人…...

【Go】Go Gin框架初识(一)

1. 什么是Gin框架 Gin框架&#xff1a;是一个由 Golang 语言开发的 web 框架&#xff0c;能够极大提高开发 web 应用的效率&#xff01; 1.1 什么是web框架 web框架体系图&#xff08;前后端不分离&#xff09;如下图所示&#xff1a; 从上图中我们可以发现一个Web框架最重要…...

计算机网络的五层协议

计算机网络的五层协议 ‌计算机网络的五层协议模型包括物理层、数据链路层、网络层、传输层和应用层&#xff0c;每一层都有其特定的功能和相关的协议。‌‌1 ‌物理层‌&#xff1a;负责传输原始的比特流&#xff0c;通过线路&#xff08;有线或无线&#xff09;将数据转换为…...

QT中,在子线程中更新UI,会出现哪些问题,如何避免这种情况发生。

在Qt中&#xff0c;直接从子线程更新UI&#xff08;用户界面&#xff09;通常会导致各种问题&#xff0c;主要是因为Qt的UI组件&#xff08;如QWidget及其子类&#xff09;并不是线程安全的。具体来说&#xff0c;可能会出现以下问题&#xff1a; 崩溃和未定义行为&#xff1a;…...

C++并发编程之多线程环境下使用无锁数据结构的重要准则

在多线程环境中使用无锁数据结构&#xff08;Lock-Free Data Structures&#xff09;能够显著提高程序的并发性能&#xff0c;因为它们避免了传统锁机制带来的竞争和阻塞问题。然而&#xff0c;无锁编程本身也带来了许多挑战&#xff0c;如内存管理、数据一致性和正确性等问题。…...

Vue篇-07

Vue UI组件库 一、移动端常用的UI组件库 1.1、Vant 1.2、Cube UI 1.3、Mint UI 二、PC端常用的UI组件库 2.1、Element UI Element - The worlds most popular Vue UI framework 安装&#xff1a; 按需引入&#xff1a; 135_尚硅谷Vue技术_element-ui按需引入_哔哩哔哩_b…...

Zookeeper 数据迁移实战:基础环境搭建与高效迁移方案全览

文章目录 一、Zookeeper数据迁移简介二、迁移zookeeper数据基础环境三、利用快照迁移zookeeper数据1、Node1最新的zk快照文件和日志文件2、将被迁移方node2的zookeeper的集群全部stop3、将源node1集群数据和日志拷贝到指定目录下4、验证优先启动拷贝的数据、日志的zookeeper节点…...

内联变量(inline variables):在多个文件中共享全局常量

在 C17 中&#xff0c;引入了 内联变量&#xff08;inline variables&#xff09; 的概念&#xff0c;可以用于在多个文件中共享全局常量。内联变量允许在头文件中定义变量&#xff0c;而不会导致链接错误&#xff08;如重复定义&#xff09;。这种方式非常适合用于定义跨多个文…...

WOA-CNN-LSTM-Attention、CNN-LSTM-Attention、WOA-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测

WOA-CNN-LSTM-Attention、CNN-LSTM-Attention、WOA-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测 目录 WOA-CNN-LSTM-Attention、CNN-LSTM-Attention、WOA-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于WOA-CNN-LSTM-A…...

【kubernetes】K8S节点状态的维护

1 节点状态 节点是K8S集群中的一类重要资源&#xff0c;节点的状态通常可以作为判断集群异常的重要手段。 为了展示节点在各方面的健康程度&#xff0c;在kubectl describe node k8s-master的输出结果中的Conditions部分可以查看k8s-master节点的一些状态数据&#xff1a; N…...

工业视觉2-相机选型

工业视觉2-相机选型 一、按芯片类型二、按传感器结构特征三、按扫描方式四、按分辨率大小五、按输出信号六、按输出色彩接口类型 这张图片对工业相机的分类方式进行了总结&#xff0c;具体如下&#xff1a; 一、按芯片类型 CCD相机&#xff1a;采用电荷耦合器件&#xff08;CC…...

Oracle查询-in条件超过1000

目录 1.不分页 2.分页 oracle数据库中&#xff0c;in的查询条件超过1000的话&#xff0c;就会报错&#xff0c;应该怎样处理这样的情况呢&#xff1f; 1.不分页 把查询条件分成几个list&#xff0c;每个list有1000个数据&#xff0c;有几个list查询几次数据库就行了 2.分…...

使用rknn进行retinaface部署(C++)

文章目录 RetinaFace导出ONNX导出RKNN编译运行学生课堂开源数据集RetinaFace RetinaFace是一种基于深度学习的高性能人脸检测方法,由InsightFace团队提出。它的核心思想是在单阶段检测器(如RetinaNet)的基础上,结合多任务学习来实现精确的人脸检测和特征点定位。以下是Ret…...

微服务拆分

微服务拆分 接下来&#xff0c;我们就一起将黑马商城这个单体项目拆分为微服务项目&#xff0c;并解决其中出现的各种问题。 熟悉黑马商城 首先&#xff0c;我们需要熟悉黑马商城项目的基本结构&#xff1a; 大家可以直接启动该项目&#xff0c;测试效果。不过&#xff0c…...

【matlab】matlab知识点及HTTP、TCP通信

1、矩阵运算 点乘&#xff1a;对于两个同维度的向量&#xff0c;点乘结果是这两个向量对应分量的乘积之和。 点除&#xff1a;是指对两个数组的对应元素进行除法运算。 点幂&#xff1a;表示元素对元素的幂运算。 >> A[1,2,3;4,5,6]; B[1,1,1;2,2,2]>> D1B.*AD…...

亿道三防丨三防笔记本是什么意思?和普通笔记本的优势在哪里?

三防笔记本是什么意思&#xff1f;和普通笔记本的优势在哪里&#xff1f; 在现代社会中&#xff0c;笔记本电脑已经成为人们工作和生活中不可或缺的一部分。然而&#xff0c;在一些特殊行业或环境中&#xff0c;普通笔记本电脑由于其脆弱性和对环境条件的敏感性&#xff0c;往…...

C++并发编程之并发可扩展性与阿姆达尔定律

在C并发编程中&#xff0c;可扩展性和阿姆达尔定律&#xff08;Amdahl’s Law&#xff09;是两个非常重要的概念&#xff0c;它们帮助我们理解和优化并发程序的性能。下面我们分别讨论这两个概念&#xff0c;并探讨它们在C并发编程中的应用。 可扩展性 可扩展性&#xff08;S…...

java 迪米特法则,原理、思想、工作流程、实现细节、稳定性、优缺点、应用场景等

迪米特法则&#xff08;Law of Demeter&#xff0c;LoD&#xff09;&#xff0c;也被称为“最少知识原则”&#xff0c;是一种指导面向对象设计的原则&#xff0c;旨在减少对象之间的耦合度。以下是对迪米特法则的详细解析。 1. 定义 迪米特法则指出&#xff1a;一个对象应该…...

使用 Docker 部署 Java 项目(通俗易懂)

目录 1、下载与配置 Docker 1.1 docker下载&#xff08;这里使用的是Ubuntu&#xff0c;Centos命令可能有不同&#xff09; 1.2 配置 Docker 代理对象 2、打包当前 Java 项目 3、进行编写 DockerFile&#xff0c;并将对应文件传输到 Linux 中 3.1 编写 dockerfile 文件 …...

DuckDB:精通Insert语句处理数据冲突

本文介绍DuckDB insert语句用法&#xff0c;包括常规的批量插入&#xff0c;尤其是插入数据冲突的处理&#xff0c;最后还提及returning子句的用法&#xff0c;每个用法提供示例说明。 insert插入数据 INSERT INTO向表中插入新行。可以插入由值表达式指定的一行或多行&#xf…...

DFT可测性设置与Tetramax测试笔记

1 DFT 1.1 DFT类型 1、扫描链&#xff08;SCAN&#xff09;&#xff1a; 扫描路径法是一种针对时序电路芯片的DFT方案.其基本原理是时序电路可以模型化为一个组合电路网络和带触发器(Flip-Flop&#xff0c;简称FF)的时序电路网络的反馈。 Scan 包括两个步骤&#xff0c;scan…...

AttributeError: Unknown IMAP4 command: ‘idle‘

imaplib 原生并不支持 IDLE 命令&#xff0c;这可能导致 AttributeError: Unknown IMAP4 command: idle 错误。解决办法是使用支持 IDLE 命令的库&#xff0c;例如 imapclient&#xff0c;或者通过扩展 imaplib 的方式实现。 以下是两种解决方案&#xff1a; 方法 1&#xff1…...

css实现响应式详解

一、媒体查询&#xff08;Media Queries&#xff09; 基本概念 媒体查询是 CSS3 中用于根据不同的设备特性&#xff08;如屏幕宽度、高度、设备类型等&#xff09;应用不同样式规则的技术。它允许你为特定的媒体类型&#xff08;如屏幕、打印、手持设备等&#xff09;和条件&a…...

hot100_240. 搜索二维矩阵 II

hot100_240. 搜索二维矩阵 II 直接遍历列减行增 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,1…...

【网络云SRE运维开发】2025第3周-每日【2025/01/15】小测-【第14章ospf高级配置】理论和实操解析

文章目录 14.1 选择题解题思路和参考答案14.2 理论题解题思路和参考答案14.3 实操题解题思路和参考答案思科&#xff08;Cisco&#xff09;设备华为&#xff08;Huawei&#xff09;设备小米/锐捷&#xff08;或其他支持标准CLI命令的设备&#xff09;通过网络管理工具注意事项 …...

c#-Halcon入门教程——标定

Halcon代码 read_image (NinePointCalibration, D:/Desktop/halcon/ca74d-main/九点标定/NinePointCalibration.gif)rgb1_to_gray (NinePointCalibration, GrayImage)get_image_size (GrayImage, Width, Height) dev_display (GrayImage)* 获取当前显示的窗口句柄 dev_get_win…...

设计和优化用于 AR、HUD 和高级显示系统的表面浮雕光栅

表面浮雕光栅是许多光学系统中的关键组件&#xff0c;在控制增强现实 &#xff08;AR&#xff09; 显示器、平视显示器 &#xff08;HUD&#xff09; 和其他先进光子器件中的光传播方面发挥着关键作用。作为在这个领域工作的工程师和设计师&#xff0c;您了解针对特定应用优化这…...

编译pytorch——cuda-toolkit-nvcc

链接 https://blog.csdn.net/wjinjie/article/details/108997692https://docs.nvidia.com/cuda/cuda-installation-guide-linux/#switching-between-driver-module-flavorshttps://forums.developer.nvidia.com/t/can-not-load-nvidia-drivers-on-ubuntu-22-10/239750https://…...

Linux 系统资源监控笔记

本文介绍如何在 Linux 系统中查看服务资源剩余情况&#xff0c;包括 CPU、内存、磁盘、网络等资源的监控方法和常用命令。 目录 查看 CPU 和内存使用情况查看磁盘使用情况查看网络使用情况查看服务资源占用查看系统整体资源使用情况图形化工具 1. 查看 CPU 和内存使用情况 使…...

在Linux系统中无网络安装Nginx并配置负载均衡

在Linux系统中无网络安装Nginx并配置负载均衡 在现代的Web开发和运维中&#xff0c;Nginx作为一个高性能的HTTP和反向代理服务器&#xff0c;被广泛应用于负载均衡、静态资源服务、SSL终端等场景。然而&#xff0c;在某些特殊环境下&#xff0c;服务器可能无法访问互联网&…...

Franka例程学习——examples_common

这一次我们学习Franka所有例程里面都要调用的examples_common.h和examples_common.cpp&#xff0c;一个是.h头文件放置声明的函数、类、变量以及宏等内容&#xff0c;.c文件里面是具体的函数实现。 一、源代码 examples_common.h // Copyright (c) 2017 Franka Emika GmbH /…...

浅谈计算机网络02 | SDN控制平面

计算机网络控制平面 一、现代计算机网络控制平面概述1.1 与数据平面、管理平面的关系1.2 控制平面的发展历程 二、控制平面的关键技术剖析2.1 网络层协议2.1.1 OSPF协议2.1.2 BGP协议 2.2 SDN控制平面技术2.2.1 SDN架构与原理2.2.2 OpenFlow协议2.2.3 SDN控制器 一、现代计算机…...

Golang概述

文章目录 1. 什么是程序2. Go语言的诞生小故事2.1 Go 语言的核心开发团队--三个大牛2.2 Google 创造 Golang 的原因2.3 Golang 的发展历程 3. Golang 的语言的特点 1. 什么是程序 程序&#xff1a;就是完成某个功能的指令的集合。画一个图理解&#xff1a; 2. Go语言的诞生小故…...

【STM8S】STM8S之IIC从机

本文最后修改时间&#xff1a;2018年10月30日 18:48 一、本节简介 本文介绍STM8S系列如何使用IIC从机接收来自IIC主机的数据。 二、实验平台 编译软件&#xff1a;IAR for STM8 1.42.2 硬件平台&#xff1a;stm8s003f3p6开发板 仿真器&#xff1a;ST-LINK 库函数版本&…...

SDK调用文心一言如何接入,文心一言API接入教程

一、前期准备 注册百度智能云账号&#xff1a; 前往百度智能云官网注册一个账号。这是接入文心一言API的基础。 了解API接口&#xff1a; 在百度智能云开放平台中&#xff0c;找到文心一言API的详情页&#xff0c;了解提供的API接口类型&#xff08;如云端API、移动端API、离线…...

实战:FRP内网穿透部署-支持ssh、web访问

目录 1 准备工作2 公网服务器部署server端2.1 frps.ini配置 3 内网客户端部署client端3.1 frpc.ini配置&#xff08;内网服务器01&#xff09;3.2 frpc.ini配置&#xff08;内网服务器02&#xff09; 4 服务启动脚本4.1 公网服务器 server4.2 内网服务器 client 2 systemctl常见…...

基于Web的宠物医院看诊系统设计与实现(源码+定制+开发)在线预约平台、宠物病历管理、医生诊疗记录、宠物健康数据分析 宠物就诊预约、病历管理与健康分析

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

Pytorch基础教程:从零实现手写数字分类

文章目录 1.Pytorch简介2.理解tensor2.1 一维矩阵2.2 二维矩阵2.3 三维矩阵 3.创建tensor3.1 你可以直接从一个Python列表或NumPy数组创建一个tensor&#xff1a;3.2 创建特定形状的tensor3.3 创建三维tensor3.4 使用随机数填充tensor3.5 指定tensor的数据类型 4.tensor基本运算…...

使用Flink-JDBC将数据同步到Doris

在现代数据分析和处理环境中&#xff0c;数据同步是一个至关重要的环节。Apache Flink和Doris是两个强大的工具&#xff0c;分别用于实时数据处理和大规模并行处理&#xff08;MPP&#xff09;SQL数据库。本文将介绍如何使用Flink-JDBC连接器将数据同步到Doris。 一、背景介绍…...

【深度学习】自编码器(Autoencoder, AE)

自编码器&#xff08;Autoencoder, AE&#xff09;是一种无监督学习模型&#xff0c;主要用于特征提取、数据降维、去噪和生成模型等任务。它的核心思想是通过将输入压缩到一个低维的潜在空间表示&#xff08;编码过程&#xff09;&#xff0c;然后再从这个潜在表示重构输入&am…...

跨专业毕业论文写作

跨专业毕业论文写作是一项具有挑战性的任务&#xff0c;但通过合理的规划和方法&#xff0c;你可以顺利完成这篇论文。以下是一些关键步骤和建议&#xff0c;帮助你撰写一篇高质量的跨专业毕业论文。 一、确定研究方向和课题 选择与本科专业相关或感兴趣的研究方向&#xff1a;…...

在 Go语言中一个字段可以包含多种类型的值的设计与接种解决方案

在 Go 中&#xff0c;如果你希望一个字段可以包含多种类型的值&#xff0c;你可以使用以下几种方式来实现&#xff1a; ### 1. **使用空接口 (interface{})** Go 的空接口 interface{} 可以接受任何类型的值&#xff0c;因此&#xff0c;你可以将字段定义为一个空接口&#x…...

为AI聊天工具添加一个知识系统 之32 三“中”全“会”:推理式的ISA(父类)和IOS(母本)以及生成式CMN (双亲委派)之1

本文要点和问题 要点 三“中”全“会”&#xff1a;推理式的ISA的&#xff08;父类-父类源码&#xff09;和IOS的&#xff08;母本-母类脚本&#xff09;以及生成式 CMN &#xff08;双亲委派-子类实例&#xff09;。 数据中台三端架构的中间端(信息系统架构ISA &#xff1a…...

手撕Transformer -- Day6 -- DecoderBlock

手撕Transformer – Day6 – DecoderBlock 目录 手撕Transformer -- Day6 -- DecoderBlockTransformer 网络结构图DecoderBlock 代码Part1 库函数Part2 实现一个解码器Block&#xff0c;作为一个类Part3 测试 参考 Transformer 网络结构图 Transformer 网络结构 DecoderBlock 代…...

Docker常用命令大全

Docker容器相关命令&#xff1a; 创建并启动容器&#xff1a; docker run&#xff1a;创建一个新的容器并运行一个命令。例如&#xff1a;docker run -d -p 8080:80 nginx这将后台(-d)运行一个Nginx容器&#xff0c;并映射宿主机的8080端口到容器的80端口。 列出容器&#x…...