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

golang gmp模型分析

思维导图:

1. 发展过程

思维导图:

在单机时代是没有多线程、多进程、协程这些概念的。早期的操作系统都是顺序执行

单进程的缺点有

  • 单一执行流程、计算机只能一个任务一个任务进行处理
  • 进程阻塞所带来的CPU时间的浪费

处于对CPU资源的利用,发展出多线程/多进程操作系统,采用时间片轮训算法

宏观上来说,就算只有一个cpu,也能并发执行多个进程

这样的好处是充分利用了CPU,但是也带来了一些问题,例如时间片切换需要花费额外的开销

  • 进程/线程的数量越多,切换成本就越大,也就越浪费

对于开发人员来说,尽管线程看起来很美好,但实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如竞争冲突

进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU虽然利用起来了,但如果进程过多,CPU有很大的一部分都被用来进行进程调度

所以提高cpu的利用率成为我们需要解决的问题

既然问题出现在线程上下文切换中,那么首先我们需要好好想一想什么是线程的上下文切换

我们知道操作系统的一些核心接口是不能被进程随意调度的,例如进行io流的读写操作,需要将最终的执行权交给操作系统(内核态)进行调度,所以就会有用户态和内核态之前的切换

这个时候我们的线程模型是这样的

一个线程需要在内核态用户态之间进行切换,并且切换是受到操作系统控制的,可能这个现在需要等待多个时间片才能切换到内核态再调用操作系统底层的接口

那么我们是否可以用两个线程分别处理这两种状态呢?两个线程之间再做好绑定,当用户线程将任务提交给内核线程后,就可以不用堵塞了,可以去执行其他的任务了

对于CPU来说(多核CPU),不需要关注线程切换的问题,只需要分配系统资源给内核线程进行调度即可

我们来给用户线程换个名字——协程(co-runtine)

如果是一比一的关系的话,其实还是可能需要等待内核线程的执行

所以可以设计为N 比 1的形式,多个协程可以将任务一股脑的交给内核线程去完成,但是这样又有问题,如果其中一个问题在提交任务的过程中,堵塞住了,就会影响其他线程的工作

这个就是python的event-loop遇到的问题,一个阻塞,其余全阻塞

所以一般为M 比 N的关系

M 比 N的关系中,大部分的精力都会放在协程调度器上,如果调度器效率高就能让协程之间阻塞时间尽可能的少

在golang中对协程调度器协程内存进行了优化

  • 协程调度器:可以支持灵活调度
  • 内存轻量化:可以拥有大量的协程

在golang早期的协程调度器中,采用的是队列的方式,M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的

老调度器有几个缺点:

  1. 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争
  2. M转移G会造成延迟和额外的系统负载。比如当G中包含创建新协程的时候,M创建了G',为了继续执行G,需要把G'交给M'执行,也造成了很差的局部性,因为G'G是相关的,最好放在M上执行,而不是其他M'
  3. 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销

2. GMP模型设计思想

思维导图:

2.1 GMP模型

GMP是goalng的线程模型,包含三个概念:内核线程(M),goroutine(G),G的上下文环境(P)

  • G:goroutine协程,基于协程建立的用户态线程
  • M:machine,它直接关联一个os内核线程,用于执行G
  • P:processor处理器,P里面一般会存当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度

在Go中,线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上

  1. 全局队列(Global Queue):存放等待运行的G
  2. P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G'时,G'优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列
  3. P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个
  4. M:线程想运行任务就得获取P,从P的本地队列获取GP队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去

P和M的数量问题

  • P的数量:环境变量$GOMAXPROCS;在程序中通过runtime.GOMAXPROCS()来设置
  • M的数量:GO语言本身限定一万 (但是操作系统达不到);通过runtime/debug包中的SetMaxThreads函数来设置;有一个M阻塞,会创建一个新的M;如果有M空闲,那么就会回收或者休眠

M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来

2.2 调度器的设计策略

golang调度器的设计策略思想主要有以下几点:

  • 复用线程
  • 利用并行
  • 抢占
  • 全局G队列
2.2.1 复用线程

golang在复用线程上主要体现在work stealing机制hand off机制(偷别人的去执行,和自己扔掉执行)

首先我们看work stealing,我们在学习java的时候学过fork/join,其中也是通过工作窃取方式来提升效率,充分利用线程进行并行计算,并减少了线程间的竞争

干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行

hand off机制

当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行,此时M1如果长时间阻塞,可能会执行睡眠或销毁

2.2.2 利用并行

我们可以使用GOMAXPROCS设置P的数量,这样的话最多有GOMAXPROCS个线程分布在多个CPU上同时运行。GOMAXPROCS也限制了并发的程度,比如GOMAXPROCS = 核数/2,则最多利用了一半的CPU核进行并行


2.2.3 抢占策略

  • 1对1模型的调度器,需要等待一个co-routine主动释放后才能轮到下一个进行使用
  • golang中,如果一个goroutine使用10ms还没执行完,CPU资源就会被其他goroutine所抢占


2.2.4 全局G队列

  • 全局G队列其实是复用线程的补充,当工作窃取时,优先从全局队列去取,取不到才从别的p本地队列取(1.17版本)

  • 在新的调度器中依然有全局G队列,但功能已经被弱化了,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G

  • 从其他队列的偷取过程是从队列尾部偷取,而队列的执行过程是顺序执行,该队列是双端队列可无锁进行。

  • 每次偷取数量为队列一半的量。

2.3 go func()经历了那些过程

  1. 我们通过go func()来创建一个goroutine
  2. 有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列
  3. G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行
  4. 一个M调度G执行的过程是一个循环机制
  5. 当M执行某一个G时候如果发生了syscall或者其他阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P
  6. 当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列

2.4 调度器的生命周期

在了解调度器生命周期之前,我们需要了解两个新的角色M0G0

M0(跟进程数量绑定,一比一):

  • 启动程序后编号为0的主线程
  • 在全局变量runtime.m0中,不需要在heap上分配
  • 负责执行初始化操作和启动第一个G
  • 启动第一个G之后,M0就和其他的M一样了

G0(每个M都会有一个G0):

  • 每次启动一个M,都会第一个创建的gourtine,就是G0
  • G0仅用于负责调度G
  • G0不指向任何可执行的函数
  • 每个M都会有一个自己的G0
  • 在调度或系统调用时会使用M切换到G0,再通过G0进行调度

M0和G0都是放在全局空间的

具体流程为:

我们来分析一段代码:

package mainimport "fmt"func main() {fmt.Println("Hello world")
}
  1. runtime创建最初的线程m0和goroutine g0,并把2者关联。
  2. 调度器初始化:初始化m0、栈、垃圾回收,以及创建和初始化由GOMAXPROCSP构成的P列表
  3. 示例代码中的main函数是main.mainruntime中也有1个main函数——runtime.main,代码经过编译后,runtime.main会调用main.main,程序启动时会为runtime.main创建goroutine,称它为main goroutine吧,然后把main goroutine加入到P的本地队列。
  4. 启动m0,m0已经绑定了P,会从P的本地队列获取G,获取到main goroutine。
  5. G拥有栈,M根据G中的栈信息和调度信息设置运行环境
  6. M运行G
  7. G退出,再次回到M获取可运行的G,这样重复下去,直到main.main退出,runtime.main执行Defer和Panic处理,或调用runtime.exit退出程序。

调度器的生命周期几乎占满了一个Go程序的一生,runtime.main的goroutine执行之前都是为调度器做准备工作,runtime.main的goroutine运行,才是调度器的真正开始,直到runtime.main结束而结束

2.5 协程的主动让渡与抢占

理解

我们已经知道,协程执行time.Sleep时,状态会从_Grunning变为_Gwaiting ,并进入到对应timer中等待,而timer中持有一个回调函数,在指定时间到达后调用这个回调函数,把等在这里的协程恢复到_Grunnable状态,并放回到runq中。

那谁负责在定时器时间到达时,触发定时器注册的回调函数呢?其实每个P都持有一个最小堆,存储在P.timers中,用于管理自己的timer,堆顶timer就是接下来要触发的那一个。

而每次调度时,都会调用checkTimers函数,检查并执行已经到时间的那些timer,不过这还不够稳妥,万一所有M都在忙,不能及时触发调度的话,可能会导致timer执行时间发生较大的偏差。

所以还会通过监控线程来增加一层保障,在介绍HelloGoroutine(GMP一)的执行过程时,我们提过监控线程是由main goroutine创建的,这个监控线程与GMP中的工作线程不同。并不需要依赖P,也不由GMP模型调度,它会重复执行一系列任务,只不过会视情况调整自己的休眠时间。其中一项任务便是保障timer正常执行,监控线程检测到接下来有timer要执行时,不仅会按需调整休眠时间,还会在空不出M时创建新的工作线程,以保障timer可以顺利执行。

当协程等待一个channel时,其状态也会从_Grunnig变成_Gwaiting,并进入到对应的channel的读队列或写队列中等待。

如果协程需要等待IO事件,就也需要让出,以epoll为例,若IO事件尚未就绪,需要注册要等待的IO事件到监听队列中,而每个监听对象都可以关联一个event data。所以就在这里记录是哪个协程在等待,等到事件就绪时再把它恢复到runq中即可 。

不过timer计时器有设置好的触发时间 ,等待的channel可读可写或关闭了,也自会通知到相关协程,而获取就绪的IO时间需要主动轮询,所以为了降低IO延迟,需要时不时的那么轮询一下,也就是执行netpoll。实际上监控线程,调度器,GC等工作过程中都会按需执行netpoll。

全局变量sched中会记录上次netpoll执行的时间,监控线程检测到距离上次轮询已超过了10ms,就会再执行一次netpoll。

上面说的无一例外,都是协程会主动让出的情况,那要是一个协程不会等待timer,channel或者IO事件,就不让出了吗?那必须不能啊,否则调度器岂不成了摆设?那怎么让那些不用等待的协程”让出“呢,这就是监控线程的另一个工作任务了,那就是本着公平调度的原则,对运行时间过长的G,实行”抢占“操作。

就是告诉那些运行时间超过特定阈值(10ms)的G,该让一让了,怎么知道运行时间过长了呢,P里面有一个schedtick字段,每当调度执行一个新的G,并且不继承上个G的时间片时,就会把它自增1,而这个p.sysmontick中,schedwhen记录的是上一次调度的时间,监控线程如果检测到p.sysmontick.schedtick与p.schedtick不相等,说明这个P又发生了新的调度,就会同步这里的调度次数,并更新这个调度时间。

但是若2者相等,就说明自schedwhen这个时间点之后,这个P并未发生新的调度,或者即使发生了新的调度,也延用了之前G的时间片,所以可以通过当前时间与schedwhen的差值来判断当前G是否运行时间过长了。

那如果真的运行时间过长了,要怎么通知它让出呢?这就不得不提到栈增长了,除了对协程栈没什么消耗的函数调用,Go语言编译器都会在函数头部插入栈相关代码。实际上编译器插入的栈增长代码一共有三种。注意这里为什么是”<=“,栈是向下增长的,上面是高地址,下面是低地址

如果栈帧比较小,插入的代码就是这样的,这个SP表示当前协程栈使用到了什么位置,stackguard0是协程栈空间下界,所以当协程栈的消耗达到或超过这个位置时,就需要进行栈增长了。

如果栈帧大小处在_StackSmall和_StackBig之间,插入的代码是这样的,也就是说,当前协程栈使用到这里,若再使用framesize这么多,超出stackguard0的部分大于_StackSmall了,就要进行栈增长了

而对于栈帧大小超过_StackBig的函数,插入的代码就有所有不同了,判断是否要栈增长的方式,本质上同第二种情况相同,而我们要关注的,是这里的stackPreempt ,它是和协程调度相关的重要标识,当runtime希望某个协程让出CPU时,就会把它的stackguard0赋值为stackPreempt。这是一个非常大的值,真正的栈指针不可能指向这个位置,所以可以安全的用作特殊标识。

正因为stackPreempt这个值足够大,所以这两段代码种的判断结果也都会为true,进而跳转到morestack处。

而morestack‘这里,最终会调用runtime.newstack函数,它负责栈增长工作,不过它在进行栈增长之前,会先判断stackguard0是否等于stackPreempt,等于的话就不进行栈增长了,而是执行一次协程调度。

所以在协程不主动让出时,也可以设置stackPreempt标识,通知它让出。

不过这种抢占方式的缺陷,就是过于依赖栈增长代码,如果来个空的for循环,因为与栈增长无关,监控线程等也无法通过设置stackPreempt标识来实现抢占,所以最终导致程序卡死。

这一问题在1.14版本中得到了解决,因为它实现了异步抢占,具体实现在不同平台种不尽相同。例如在Unix平台中,会向协程关联的M发送信号(sigPreempt),接下来目标线程会被信号中断,转去执行runtime.sighandler,在sighandler函数中检测到函数信号为sigPreempt后,就会调用runtime.doSigPreempt函数,它会向当前被打断的协程上下文中,注入一个异步抢占函数调用,处理完信号后sighandler返回,被中断的协程得以恢复,立刻执行被注入的异步抢占函数, 该函数最终会调用runtime中的调度逻辑,这不就让出了嘛。所以在1.14版本中,这段代码执行之前就不会卡死了。

而监控线程的抢占方式又多了一种,异步抢占,其实为了充分利用CPU,监控线程还会抢占处在系统调用中的P,因为一个协程要执行系统调用,就要切换到g0栈,在系统调用没执行完之前,这个M和这个G算是抱团了,不能被分开,也就用不到P,所以在陷入系统调用之前,当前M会让出P,解除m.p与当前p的强关联,只在m.oldp中记录这个p,P的数目毕竟有限,如果有其他协程在等待执行,那么放任P如此闲置就着实浪费了,还是把它关联到其他M继续工作比较划算,不过如果当前M从系统调用中恢复,会先检测之前的P是否被占用,没有的话就继续使用,否则就再去申请一个,没申请到的话,就把当前G放到全局runq中去,然后当前线程m就睡眠了。

说了这么多,不是让出就是抢占。

那让出了,抢占了之后,M也不能闲着,得找到下一个待执行的G来运行,这就是schedule()的职责了。schedul这里要给这个M找到一个待执行的G,首先要确定当前M是否和当前G绑定了,如果绑定了,那当前M就不能执行其他G,所以需要阻塞当前M,等到当前G再次得到调度执行时,自会把当前M唤醒。如果没有绑定,就先看看GC是不是在等待执行,全局变量sched这里,有一个gcwaiting标识,如果GC在等待执行,就去执行GC,回来再继续执行调度程序。接下来还会检查一下有没有要执行的timer。调度程序还有一定几率会去全局runq中获取一部分G到本地runq中。

而获取下一个待执行的G时,会先去本地runq中查找,没有的话,就调用findrunnable(),这个函数直到获取到待运行的G才会返回。在findrunnable()函数这里,也会判断是否要执行GC,然后先尝试从本地runq中获取,没有的话就从全局runq获取一部分,如果还没有,就先尝试执行netpoll,恢复那些IO事件已经就绪了的G,它们会被放回到全局runq中,然后才会尝试从其他P那里steal一些G 。

当调度程序终于获得一个待执行的G以后,还要看看人家有没有绑定的M,如果有的话还得乖乖的把G还给对应的M。而当前M就不得不再次进行调度了。如果没有绑定的M,就调用excute函数在当前M上执行这个G。excute函数这里会简历当前M和这个G的关联关系,并把G的状态从_Grunnable修改为_Grunning,如果不继承上一个执行中协程的时间片,就把P这里的调度计数加一,最后会调用gogo函数,从g.sched这里恢复协程栈指针,指令指针等,接着继续协程的执行。

之前介绍过,协程创建时,会伪装一个执行现场存到g.sched中,所以即使这个G初次执行,也是有一个完美的执行现场的。

现在我们已经知道,协程在某些情况下会主动让出,但有时也需要设置stackPreemt标识,或异步抢占的方式来通知它让出。也了解了调度程序如何获取待执行的G并把它运行起来。期间还穿插介绍了监控线程的主要工作任务”保障计时器正常工作,执行网络轮询,抢占长时间运行的,或处在系统调用的P“,这些都是为了保障程序健康高效的执行,其实监控线程还有一项任务,就是强制执行GC,待到内存管理部分再展开~

2.6 可视化的CMP编程

2.6.1 trace方式

在这里我们需要使用trace编程,三步走:

  1. 创建trace文件:f, err := os.Create("trace.out")
  2. 启动trace:err = trace.Start(f)
  3. 停止trace:trace.Stop()

然后再通过go tool trace工具打开trace文件

go tool trace trace.out
package mainimport ("fmt""os""runtime/trace"
)// trace的编码过程
// 1. 创建文件
// 2. 启动
// 3. 停止
func main() {// 1.创建一个trace文件f, err := os.Create("trace.out")if err != nil {panic(err)}defer func(f *os.File) {err := f.Close()if err != nil {panic(err)}}(f)// 2. 启动traceerr = trace.Start(f)if err != nil {panic(err)}// 正常要调试的业务fmt.Println("hello GMP")// 3. 停止tracetrace.Stop()
}

打开后我们进入网页点击view trace,然后就能看到分析信息

G的信息

M的信息

P的信息

2.6.2 debug方式

使用debug方式可以不需要trace文件

先搞一段代码

package mainimport ("fmt""time"
)func main() {for i := 0; i < 5; i++ {time.Sleep(time.Second)fmt.Println("hello GMP")}
}

debug执行一下

$ GODEBUG=schedtrace=1000 ./debug.exe
SCHED 0ms: gomaxprocs=8 idleprocs=7 threads=6 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
SCHED 1008ms: gomaxprocs=8 idleprocs=8 threads=6 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
hello GMP
SCHED 2009ms: gomaxprocs=8 idleprocs=8 threads=6 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
hello GMP
SCHED 3010ms: gomaxprocs=8 idleprocs=8 threads=6 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
hello GMP
hello GMP
SCHED 4017ms: gomaxprocs=8 idleprocs=8 threads=6 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
hello GMP
  • SCHED:调试信息输出标志字符串,代表本行是goroutine调度器的输出;
  • 0ms:即从程序启动到输出这行日志的时间;
  • gomaxprocs: P的数量,本例有2个P, 因为默认的P的属性是和cpu核心数量默认一致,当然也可以通过GOMAXPROCS来设置;
  • idleprocs: 处于idle状态的P的数量;通过gomaxprocs和idleprocs的差值,我们就可知道执行go代码的P的数量;
  • threads: os threads/M的数量,包含scheduler使用的m数量,加上runtime自用的类似sysmon这样的thread的数量;
  • spinningthreads: 处于自旋状态的os thread数量;
  • idlethread: 处于idle状态的os thread的数量;
  • runqueue=0: Scheduler全局队列中G的数量;
  • [0 0]: 分别为2个P的local queue中的G的数量。

参考文章

GolangGMP模型 GMP(三):协程让出,抢占,监控与调度 - cheems~ - 博客园

https://github.com/fengyuan-liang/notes/blob/main/GoLang/golang%E5%A4%A7%E6%9D%80%E5%99%A8GMP%E6%A8%A1%E5%9E%8B.md

相关文章:

golang gmp模型分析

思维导图&#xff1a; 1. 发展过程 思维导图&#xff1a; 在单机时代是没有多线程、多进程、协程这些概念的。早期的操作系统都是顺序执行 单进程的缺点有&#xff1a; 单一执行流程、计算机只能一个任务一个任务进行处理进程阻塞所带来的CPU时间的浪费 处于对CPU资源的利用&…...

Redisson的RedLock与联锁(MultiLock)的区别

Redisson提供了两种分布式锁机制&#xff1a;RedLock(红锁)和MultiLock(联锁)&#xff0c;它们在实现分布式锁时有重要区别。 1. RedLock (红锁) 设计原理&#xff1a; 基于Redis官方提出的Redlock算法实现目的是在Redis集群环境下提供更可靠的分布式锁需要至少3个独立的Redi…...

图灵逆向——题十-魔改算法

目录列表 过程分析JS代码还原代码实现运行结果 本题属于魔改标准加密算法&#xff0c;所以无法使用JS或Python中的标准库来进行模拟加密了&#xff0c;只能一步一步的还原它的环境咯。。。 过程分析 打开控制台发现有个无限debugger&#xff0c;直接过掉~[doge]。。。 OK过掉…...

K8S学习之基础七十九:关闭istio功能

关闭istio功能 kubectl get ns --show-labels kubectl label ns default istio-injection-有istio-injectionenabled的命名空间&#xff0c;pod都会开启istio功能 反之&#xff0c;如果要开启istio&#xff0c;在对应命名空间打上该标签即可...

AI大模型课程系列汇总-某客时间篇

某客时间系列篇章&#x1f4a5; NO.文章&#xff08;点击可跳转&#xff09;1【课程系列11】某客时间AI 大模型应用开发实战营_ai大模型应用开发实战营 百度云-CSDN博客2【课程系列12】某客时间AI大模型微调训练营_极客时间 大模型rag 网盘-CSDN博客3【课程系列15】2024某客时…...

【人工智能】大语言模型多义词解析技术揭秘——以“项目“歧义消解为例

今天田辛老师和小伙伴探讨了一个有趣的多义词问题&#xff0c; 在人工智能技术日新月异的今天&#xff0c;大语言模型&#xff08;LLM&#xff09;对自然语言的理解能力已经达到令人惊叹的水平。大模型到底是如何去区分多义词的&#xff1f; 比如&#xff1a;当用户提到"…...

Coze平台 发布AI测试Agent的完整实现方案

以下是基于 Coze平台 发布AI测试Agent的完整实现方案,包含架构设计、核心功能模块、集成方式和落地步骤: 一、方案架构图 #mermaid-svg-kzS7cltxrbetjROl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-kzS7cltxr…...

OpenHarmony子系统开发 - 调测工具(二)

OpenHarmony子系统开发 - 调测工具&#xff08;二&#xff09; 三、hiperf使用指导 hiperf是为开发人员提供性能采样分析的工具&#xff0c;基于内核perf机制进行的用户态能力的扩展&#xff0c;可以对指定的程序或者整个系统进行性能采样。 hiperf支持的命令有&#xff1a;l…...

keil ERROR: L6220E 如何解决

最近调试MK8000方案&#xff0c;增加code后遇到&#xff1a; keil ERROR: L6220E: Execution region RW_RAM1 size (24592 bytes) execeeds limit (24568 bytes) .region contains 93 bytes of padding and 0 bytes of veneers (total 93 bytes of linker generated content) …...

c++比较器——priority_queue用 ; unordered_map 自定义哈希函数

文章目录 priority_queue自定义比较方法对 比较对象结构体 重载 <仿函数为什么是传一个类std::less<T> 和 std::greater<T> lambda unordered_map自定义哈希函数仿函数lambda priority_queue template<class T,class Container std::vector<T>,class…...

centos-LLM-生物信息-BioGPT安装

参考&#xff1a; GitHub - microsoft/BioGPT https://github.com/microsoft/BioGPT BioGPT&#xff1a;用于生物医学文本生成和挖掘的生成式预训练转换器 |生物信息学简报 |牛津学术 — BioGPT: generative pre-trained transformer for biomedical text generation and mini…...

esp32cam远程图传:AI Thinker ESP32-CAM -》 服务器公网 | 服务器 -》 电脑显示

用AI Thinker ESP32-CAM板子访问公网ip的5112端口并上传你的摄像头拍摄的图像视频数据&#xff0c;并写一段python程序打开弹窗接受图像实现超远程图像传输教程免费 1. 首先你要有一个公网ip也就是去买一台拥有公网的服务器电脑&#xff0c;我买的是腾讯云1年38元的服务器还可…...

今日踩坑之@Autowired与@Resource区别

案例说明 今天算是体验到了这俩注解的区别了&#xff0c;真所谓不报错就记不住这两注解区别&#xff0c;其实之前本人也是知道这俩注解的区别的只是我们往往项目上用习惯了Autowired就忘了&#xff0c;看来只有真正体会到报错才能记忆犹新o(╥﹏╥)o。 说明一下本人的踩坑情况&…...

在 M1 芯片的 Mac 电脑上安装 Redis 可以通过 ​​Homebrew​​ 快速完成

以下是详细步骤&#xff1a; 1. 安装 Homebrew&#xff08;若未安装&#xff09; 打开 ​​终端​​&#xff0c;执行以下命令安装 Homebrew&#xff08;已安装可跳过&#xff09;&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/…...

【从零开始学习JVM | 第一篇】快速认识JVM

什么是JVM&#xff1f; JVM--Java虚拟机&#xff0c;它是Java实现平台无关性的基石。 Java程序运行的时候&#xff0c;编译器将Java代码编译为平台无关的Java字节码文件&#xff08;.class&#xff09;&#xff0c;接下来对应平台的JVM对字节码进行运行解释&#xff0c;翻译成…...

linux 里的创建了一个tomcat用户 怎么禁止该用户使用crontab 功能

在 Linux 系统中&#xff0c;可以通过以下方法禁止 tomcat 用户使用 crontab 功能&#xff1a; 方法 1&#xff1a;通过 /etc/cron.deny 文件限制 这是 最推荐的标准方法&#xff0c;适用于所有主流 Linux 发行版&#xff08;CentOS、Ubuntu 等&#xff09;。 操作步骤&#…...

Elasticsearch入门指南(二)

一、RestClient操作索引库 什么是RestClient&#xff1f; ES官方提供了各种不同语言的客户端&#xff0c;用来操作ES。这些客户端的本质就是组装DSL语句&#xff0c;通过http请求发送给ES。官方文档地址&#xff1a; https://www.elastic.co/guide/en/elasticsearch/client/in…...

Mac监控新风尚:酷炫界面,性能监控更直观!

你是否曾经希望自己的Mac能够像Windows那样&#xff0c;轻松一按就能查看任务管理器来监控性能状态呢&#xff1f; 对于Mac用户来说&#xff0c;系统性能监控一直是个挑战——苹果公司并没有提供一个直观的性能监控工具&#xff0c;用户往往需要通过复杂的活动监视器来获取这些…...

碰一碰发视频源码开发深度解析,定制化开发

在移动应用开发领域&#xff0c;便捷的数据分享功能始终备受关注。碰一碰发视频这一创新功能&#xff0c;借助近场通信&#xff08;NFC&#xff09;技术&#xff0c;为用户提供了一种快速、直观的视频分享体验。本文将深入剖析碰一碰发视频功能的源码开发过程&#xff0c;涵盖从…...

基于 AI智能体、大模型、RAG、Agent 等技术构建公司内部闭环智能问答系统的详细方案,结合 Spring Boot + Vue 管理系统 的改造思路

以下是基于 AI智能体、大模型、RAG、Agent 等技术构建公司内部闭环智能问答系统的详细方案&#xff0c;结合 Spring Boot Vue 管理系统 的改造思路&#xff1a; 1. 系统架构设计 核心组件 数据源层 内部文档库&#xff1a;公司知识库&#xff08;如操作手册、FAQ、流程文档&a…...

红宝书第三十四讲:零基础学会单元测试框架:Jest、Mocha、QUnit

红宝书第三十四讲&#xff1a;零基础学会单元测试框架&#xff1a;Jest、Mocha、QUnit 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、单元测试是什么&#xff1f; 就像给代码做“体检”&#xff0c;帮你检查…...

视觉分析AI赋能智慧水务多场景应用

利用视觉分析的方式智慧水务的AI算法方案 一、背景 在传统水务监管中&#xff0c;面临着诸多痛点。例如&#xff0c;对于河道污染、水面异常、河湖四乱问题以及水生态环境差等情况&#xff0c;传统监管效率低下&#xff0c;预警不及时&#xff0c;资源分散&#xff0c;监控功…...

网络2 IP与MAC IP地址

IP地址与MAC地址 一 1.关系&#xff1a; 网卡有唯一的物理地址&#xff1a;MAC地址&#xff0c;IP是配置在网卡上的逻辑地址。 IP地址、MAC地址是进行网络通信必不可少的 2.MAC地址是物理地址&#xff0c;不可更改&#xff0c;通常与IP地址绑定 3.MAC地址&#xff1a;48位 IP地…...

spark-Core

运行架构 核心组件 由上图可以看出&#xff0c;对于 Spark 框架有两个核心组件&#xff1a; Driver&#xff0c;Spark 驱动器节点 其中Spark 驱动器节点&#xff0c;用于执行 Spark 任务中的 main 方法&#xff0c;负责实际代码的执行工作。 Driver 在 Spark 作业执行时主要…...

前端开发中的问题排查与定位:HTML、CSS、JavaScript(报错的解决方式)

目录 1.html 1. 结构错误调试&#xff1a;标签未正确嵌套 2. 语法问题调试&#xff1a;缺失引号 3. 断点调试&#xff1a;动态生成内容时的 JavaScript 错误 4. 网络调试&#xff1a;资源加载错误 5. 性能调试&#xff1a;页面加载性能 总结&#xff1a; 2.CSS 1. 定位…...

VMware Fusion Pro 13 for Mac虚拟机

VMware Fusion Pro 13 for Mac虚拟机 文章目录 VMware Fusion Pro 13 for Mac虚拟机一、介绍二、效果下载 一、介绍 VMware Fusion Pro for Mac&#xff0c;是一款mac虚拟机软件&#xff0c;跟Parallels Desktop一样&#xff0c;都可以让你的 Mac 同时运行一个或多个不同的操作…...

使用cline(VSCode插件)、continue(IDEA插件)、cherry-studio玩转MCP

安装环境 uv&#xff08;python&#xff09; 为什么不用pip&#xff1f; 使用 uv 时无需进行特定安装。使用 uvx 直接运行。 ⚡️ 比pip快10-100x https://github.com/pypa/pip https://ossinsight.io/analyze/pypa/pip?vsastral-sh%2Fuv#overview 安装 https://github…...

Kotlin FragmentTransaction多容器管理多个fragment

在Activity中管理五个Fragment的切换显示和隐藏&#xff0c;并且希望将这部分逻辑进行封装。之前已经教过他们如何在Kotlin中使用FragmentTransaction进行基本的添加、隐藏、显示和替换操作&#xff0c;现在需要进一步封装这些操作&#xff0c;提高代码的可维护性和复用性。 管…...

PyCharm显示主菜单和工具栏

显示主菜单 新版 PyCharm 是不显示主菜单的&#xff0c;要想显示主菜单和工具栏&#xff0c;则通过 “视图” → “外观” &#xff0c;勾选 “在单独的工具栏中显示主菜单” 和 “工具栏” 即可。 设置工具栏 此时工具栏里并没有什么工具&#xff0c;因此我们需要自定义工具…...

WebView2最低支持.NET frame4.5,win7系统

WebView2最低支持.NET frame什么版本 ‌WebView2 对 .NET Framework 的最低版本要求‌ ‌基础支持范围‌ WebView2 官方支持的 .NET Framework ‌最低版本为 4.5‌&#xff0c;同时兼容 ‌.NET Core 3.0‌ 及以上版本‌18。对于 WPF、WinForms 等桌面应用开发&#xff0c;需确…...

ClickOnce 部署

1、在远程服务器172.16.9.252共享文件文件夹Bluetooth. 2、设置版本自动更新. 3、设置部署 4、设置创建桌面菜单 二、远程发布IIS即可...

Kotlin 中 集合 Collection 的扩展方法完全指南

Kotlin 中 Collection 的扩展方法完全指南 “代码是最美的诗篇”——本文将带你进入 Kotlin 集合扩展函数的世界&#xff0c;帮助你写出既高效又优雅的代码 &#x1f680; 一、引言 &#x1f914; 在 Android 开发中&#xff0c;集合&#xff08;Collection&#xff09;的操作…...

STM32F407使用ESP8266连接阿里云并上传数据

文章目录 前言一、ESP01S介绍1.ESP01S2.MQTT固件烧录3.WIFI连接 二、阿里云平台介绍1.创建产品及添加设备2.连接云平台 三、数据上报四、命令下发五、完整工程 前言 在实现OTA功能我们必须学会如何连接云平台&#xff0c;本文会仔细介绍使用STM32F407和ESP8266连接阿里云平台&…...

TorchServe部署模型-index_to_name.json

在TorchServe部署模型时&#xff0c;若要将模型输出结果映射到指定标签&#xff08;如分类任务的类别名称&#xff09;&#xff0c;需通过index_to_name.json文件定义索引与标签的映射关系&#xff0c;并在打包模型时将其作为额外文件包含。以下是完整流程和命令示例&#xff1…...

每日一题——BMN3 小红炸砖块

“落下”操作只会对y轴有影响&#xff0c;所以注意y轴的变化即可 只要给出的坐标有砖块&#xff0c;就遍历查找他的上面是否有砖块&#xff0c;每一层都是这样&#xff0c;直到到没有砖块的那一层&#xff1b; 注意&#xff1a;定义矩阵时要注意&#xff0c;给出的坐标都是大…...

AWS服务器 磁盘空间升级到100G后,怎么使其生效?

在AWS&#xff08;Amazon Web Services&#xff09;上扩展EBS&#xff08;Elastic Block Store&#xff09;卷的大小后&#xff0c;服务器操作系统并不会自动识别新增的空间。要使操作系统识别并使用新增的磁盘空间&#xff0c;您需要进行一些额外的步骤。以下是详细的指导和说…...

AWS弹性容器服务(AWS Elastic Container Service,ECS)概述

李升伟 编译 标签&#xff1a;AWS | ECS | 容器 | Docker AWS弹性容器服务&#xff08;AWS Elastic Container Service&#xff0c;ECS&#xff09;简介 AWS弹性容器服务&#xff08;ECS&#xff09;是一项完全托管的容器编排服务&#xff0c;支持运行、管理和扩展容器化应用…...

【消息队列kafka_中间件】一、快速入门分布式消息队列

在当今大数据和分布式系统盛行的时代&#xff0c;消息队列作为一种关键的中间件技术&#xff0c;发挥着举足轻重的作用。其中&#xff0c;Apache Kafka 以其卓越的性能、高可扩展性和强大的功能&#xff0c;成为众多企业构建分布式应用的首选消息队列解决方案。本篇文章将带你深…...

【Axure元件分享】移动端滑动拨盘地区级联选择器

在移动端产品设计中&#xff0c;地区级联选择器&#xff08;省/市/区&#xff09;是用户信息录入场景的核心组件&#xff0c;尤其在电商收货地址、政务信息填报等高频业务中直接影响表单转化率。本文将介绍一款基于Axure的三级动态联动机型地区选择器&#xff0c;通过仿真级联滚…...

宁德时代25年校招演绎数字推理SHL测评题库题型及真题分析

非常感谢您对宁德时代的关注。祝贺您通过宁德时代校园招聘的专业面试环节&#xff0c;现邀请您参与完成以下测评。本轮共两份测评&#xff0c;每份测评对您的最终结果都非常重要&#xff0c;请务必在收到测评后48小时内完成!本测评需要进行远程监考&#xff0c;如果您无法或不愿…...

Python-Django+vue宠物服务管理系统功能说明

❥(^_-) 上千个精美定制模板,各类成品Java、Python、PHP、Android毕设项目,欢迎咨询。 ❥(^_-) 程序开发、技术解答、代码讲解、文档,💖文末获取源码+数据库+文档💖 💖软件下载 | 实战案例 💖文章底部二维码,可以联系获取软件下载链接,及项目演示视频。 本项目…...

洛谷普及B3691 [语言月赛202212] 狠狠地切割(Easy Version)

题目&#xff1a;[语言月赛202212] 狠狠地切割(Easy Version) 题号&#xff1a;B3691 难度&#xff1a;普及一 末尾包含对二分法优化的详细解释 题目分析 最后一句应该是本题的考查关键&#xff0c;关于筛选算法的时间优化&#xff0c; 但从功能理论上&#xff0c;我找到了…...

FPGA_BD Block Design学习(一)

PS端开发流程详细步骤 1.第一步&#xff1a;打开Vivado软件&#xff0c;创建或打开一个工程。 2.第二步&#xff1a;在Block Design中添加arm核心&#xff0c;并将其配置为IP核。 3.第三步&#xff1a;配置arm核心的外设信息&#xff0c;如DDR接口、时钟频率、UART接口等。 …...

Collection vs Collections:核心区别与面试指南

Collection vs Collections&#xff1a;核心区别与面试指南 一、本质区别&#xff08;核心记忆点&#xff09; 维度CollectionCollections身份集合框架的根接口操作集合的工具类包位置java.utiljava.util是否可实例化❌ 接口✅ 类&#xff08;但构造器私有&#xff0c;不可实…...

sqlite3基本语句

创建表 CREATE TABLE student ( id INTEGER PRIMARY KEY, -- 学号&#xff0c;主键 name TEXT NOT NULL, -- 姓名&#xff0c;不能为空 age INTEGER, -- 年龄 gender TEXT -- 性别 ); SQLite常用数据类型 主键 …...

jupyter notebook 显示conda虚拟环境

使用 nb_conda_kernels 安装 nb_conda_kernels&#xff1a;这个包可以自动从你的 Conda 环境中发现并列出内核。 conda activate base # 确保你在 base 环境或任何其他环境中安装 conda install nb_conda_kernels显示jupyternotebook当前所在的位置。...

华为海思IC前端中后端(COTXPU)岗位笔试机考题

近期华为海思即将开始IC设计实现实习岗位机考。小编今天给大家分享下华为海思相关机考题目。 华为海思2025届校招笔试面试经验分享 每年IC秋招笔试其实也是从题库中随机抽出一些题。 华为海思2025届校招笔试面试经验分享华为海思机考主要分三个方向&#xff0c;分别是物理方向…...

HarmonyOS NEXT 实现滑动拼图验证码功能

大家好&#xff0c;我是 V 哥。 在 Gitee 上看到一个用 Java 实现的 HarmonyOS 滑动拼图验证码功能&#xff0c;已经太老了&#xff0c;鸿蒙开发推荐使用 ArkTS 语言&#xff0c;V 哥来改造一下。 以下是基于 ArkTS 的实现方案。由于鸿蒙系统的特性差异&#xff0c;这里提供核…...

【XML基础-1】深入理解XML:介绍、语法规则与实际应用

XML&#xff08;可扩展标记语言&#xff09;作为数据表示和交换的标准格式&#xff0c;自1998年问世以来已成为现代计算领域不可或缺的一部分。本文将全面介绍XML的基础概念、详细语法规则以及它在各领域中的实际应用。 1. 什么是XML&#xff1f; XML&#xff08;eXtensible …...

STM32 HAL库扩大USB CDC的输入缓冲区

STM32 HAL库,使用USB, 扩大输入暂存区的方法 使用STM32的USB通讯CubeMX建立配置Serial Wire时钟配置USB配置时钟频率设置代码编写运行效果总结使用STM32的USB通讯 STM32可以不用使用串口转换直接和USB通讯。这给串口调试提供了极大的方便。编程,我使用了STM32CubeIDE编程。这…...