Go语言入门基础:协程
第21章 协程
目录
- 21.1 启动Go协程
- 协程的概念与特点
- 启动协程的方法及示例
- 协程执行顺序的控制
- 21.2 通道
- 通道类型及表示方式
- 21.2.1 实例化通道
- 21.2.2 数据缓冲
- 21.2.3 单向通道
- 21.2.4 通道与select语句
- 21.3 互斥锁
- 协程并发访问的逻辑问题
- 互斥锁的使用示例
- 21.4 WaitGroup类型
- WaitGroup类型的原理
- WaitGroup类型的使用示例
21.1 启动Go协程
Go语言的异步操作引入了协程(原单词为goroutines
)的概念,启动新的协程后会异步执行指定的函数。协程类似于线程,但它并非与线程一一对应,而是由Go运行时内部进行调度,有可能一个线程会运行多个协程,也有可能一个协程在不同线程间切换,这一切都是Go运行时自动分配和控制的。
Go协程没有名称,也没有ID值,程序代码无法获取协程的唯一标识。
应用程序在运行时至少会启动一个协程——执行main
函数的协程,此协程可以称为主协程,当该协程执行完毕后,整个程序就会退出。
在代码中开启新协程的方法很简单,只要在调用函数或方法时加上“go
”关键字即可。例如:
func test1() {fmt.Print("春")
}func test2() {fmt.Print("夏")
}func test3() {fmt.Print("秋")
}func test4() {fmt.Print("冬")
}func main() {// 启动四个新的协程go test1()go test2()go test3()go test4()
}
但是,运行上面代码后,屏幕上可能什么内容都没有输出。这是因为新开启的四个协程与主协程(共五个协程)都是独立执行的,四个新协程还没来得及打印消息,main
函数就退出了,进而导致整个程序的退出,所以屏幕上看不到任何输出。
在main
函数的最后增加一行对time.Sleep
函数的调用,让主协程暂停1秒钟。
func main() {// 启动四个新的协程go test1()go test2()go test3()go test4()// 暂停一下time.Sleep(time.Second)
}
由于主协程的执行时间被延长了,使得新启动的四个协程能够完成执行,再次运行上述程序,就能看到以下输出了:
春秋冬夏
此时,读者会发现,“春”“夏”“秋”“冬”四个字符的输出是无序的。不妨再执行三次上述程序,看看输出结果。
春冬秋夏 // 第一次
秋夏冬春 // 第二次
夏春秋冬 // 第三次
这是因为协程之间不仅是相互独立的,而且代码运行的顺序是随机的。如果需要控制它们的顺序,可以使用通道(channel
)。将上述程序代码进行以下修改:
var (A = make(chan int)B = make(chan int)C = make(chan int)D = make(chan int)
)func test1() {fmt.Print("春")A <- 1
}func test2() {<-Afmt.Print("夏")B <- 1
}func test3() {<-Bfmt.Print("秋")C <- 1
}func test4() {<-Cfmt.Print("冬")D <- 1
}func main() {// 启动四个新的协程go test1()go test2()go test3()go test4()<-D
}
变量A、B、C、D均为通道类型,支持传递int
类型的值。四个通道都是无缓冲的,通道的输入与输出必须同时进行,也就是说,如果有int
值写入通道,就得要求有其他对象同时将该值读出。
启动test1
函数时,test2
函数要等待通道A中的有输入的值才能读出,于是,test2
被阻塞(不能往后执行),test1
函数输出“春”之后,将整数值1写入通道A,此时test2
中顺利读到了通道A的值,就可以往下执行。
test2
、test3
、test4
函数的原理也一样,可以用下图来表示此过程。运行代码,这一次能得到预期的结果了。
春夏秋冬
21.2 通道
在Go的异步编程中,通道类型(channel
,类型名称为chan
)既可以用于协程之间的数据通信,也可以用于协程之间的同步。
通道类型有以下几种表示方式:
chan T
// 双向通道,既可以发送数据,也可以接收数据chan <- T
// 只能向通道发送数据<- chan T
// 只能从通道接收数据
其中,T
是通道中可存放的数据类型。例如:
chan int
上述格式表示双向通道,通道可以存放int
类型的值。
通道数据的输入输出是通过“<-
”运算符(称作“接收运算符”)来完成的。“<-
”位于通道变量之前表示从通道中接收数据;“<-
”位于通道变量后面表示向通道发送数据。
var ch = ……
ch <- 5 // 向通道发送数据
var n = <-ch // 从通道接收数据
注意上述代码中,<-ch
表达式仅表示从通道ch
中读出数据,若要将读出的值赋值给变量n
,则必须使用赋值运算符(=
)。
21.2.1 实例化通道
通道对象的实例是通过make
函数创建的,此函数可以创建切片、映射、通道类型的实例。
下面的代码创建一个可存储string
类型数据的通道实例。
var c = make(chan string)
也可以这样:
var c = make(chan string, 0)
make
函数的第二个参数(size
)表示通道对象的缓冲值,忽略此参数或者设置为0表示所创建的通道实例不使用缓冲。
下面的语句向通道发送数据。
c <- "hello"
然后可以从通道接收数据。
<-c
当不再使用通道实例时,可以调用close
函数将其关闭。
close(c)
通道常用于不同协程之间的通信,在同一个协程中使用通道意义不大。
21.2.2 数据缓冲
无缓冲的通道要求发送与接收操作同时进行——向通道发送数据的同时必须有另一个协程在接收。
请看下面的例子。
func main() {// 创建通道实例var mych = make(chan uint)// 启动新的协程go func() {fmt.Println("开始执行新协程")// 向通道发送数据mych <- 350fmt.Println("新协程执行完毕")}()// 暂停一下time.Sleep(time.Second)fmt.Println("主协程即将退出")
}
代码运行后输出的内容如下:
开始执行新协程
主协程即将退出
main
协程等待了1秒钟后退出,从输出结果可以看出,新启动的协程并没有完全被执行。程序在向通道mych
发送数据后就被阻塞,无法继续执行,这是因为整数350发送到通道后没有被及时被读取所致,解决方法是在main
函数中接收通道中的数据。修改后的代码如下:
func main() {// 创建通道实例var mych = make(chan uint)// 启动新的协程go func() {……}()// 从通道中接收数据<-mych……
}
由于新创建的协程与主协程是异步执行的,使得通道mych
的发送与接收行为可以同时完成,程序不会被阻塞,最终两个协程都顺利执行。
带缓冲的通道的读与写可以不同时进行,举个例子说明。
func main() {// 创建通道实例var mych = make(chan string, 1)go func() {fmt.Println("开始执行新的协程")// 向通道发送数据mych <- "Hello"fmt.Println("新协程执行完毕")}()// 暂停一下time.Sleep(time.Second * 2)fmt.Println("主协程即将退出")
}
这一次,虽然在主协程上没有接收通道中的数据,但程序可以正常完成执行,输出结果如下:
开始执行新的协程
新协程执行完毕
主协程即将退出
这是因为此次创建的通道实例是带缓冲的,缓冲的元素个数为1。所以,当新的协程代码向通道发送了一次数据后,数据会缓存在通道中,不要求立即被取出,代码就不会被阻塞——哪怕main
函数中未接收通道的数据也不会阻塞。
不过,若是通道中缓存的数据量已满,再次向通道发送数据就会被阻塞,直到数据被接收为止。就像下面这样:
func main() {// 创建通道实例var mych = make(chan string, 1)go func() {fmt.Println("开始执行新的协程")// 向通道发送数据mych <- "Hello"// 再发送一次就会阻塞mych <- "World"fmt.Println("新协程执行完毕")}()……
}
如果将make
函数的调用修改为:
var mych = make(chan string,2)
那么此时的缓存容量为2,发送两次数据不会被阻塞,当第三次向通道发送数据时就会阻塞。
21.2.3 单向通道
请看下面的代码。
var ch1 = make(<-chan bool)
var ch2 = make(chan<- bool)
ch1
为单向通道实例,只能从通道接收数据,不能向通道发送数据;ch2
也是单向通道实例,只能向通道发送数据,不能接收数据。
直接在代码中使用单向通道没有意义,因为数据无法完成输入和输出。不过,要是用于代码封装,作为数据进出的间接通道,单向通道就很合适。
下面的示例演示了单向通道的使用。
步骤1:定义demo
包,公开C
变量和Start
函数。
package demoimport "time"// 此变量仅在包内访问
var innerch = make(chan int, 1)
21.2.3 单向通道
var ch1 = make(<-chan bool)
var ch2 = make(chan<- bool)
ch1
为单向通道实例,只能从通道接收数据,不能向通道发送数据;ch2
也是单向通道实例,只能向通道发送数据,不能接收数据。
直接在代码中使用单向通道没有意义,因为数据无法完成输入和输出。不过,要是用于代码封装,作为数据进出的间接通道,单向通道就很合适。
下面的示例演示了单向通道的使用。
步骤1: 定义demo
包,公开C
变量和Start
函数。
package demoimport "time"// 此变量仅在包内访问
var innerch = make(chan int, 1)
// 此变量对外公开
var C <-chan int = innerch
// 此函数对外公开
func Start() {time.Sleep(time.Second * 2)innerch <- 10000
}
innerch
是双向通道,但只有demo
包内部才能访问。对外公开变量C
,类型为单向通道。经过封装后,外部代码只能访问C
来接收数据,不能发送数据,这样可以防止demo
包内部的数据被意外修改。
外部代码在使用demo
包时,先调用Start
函数,2秒钟后向通道(innerch
)发送整数值10000,此时外部代码只能通过变量C
来接收通道中的数据。
步骤2: 在主包中导入demo
包。
import (……"./demo"
)
步骤3: 在main
函数中先调用Start
函数,接着通过变量C
接收数据。
func main() {fmt.Println("等待结果……")demo.Start()var x = <-demo.Cfmt.Println("结果出来了")fmt.Printf("结果: %d\n", x)
}
步骤4: 运行示例程序,结果如下:
等待结果……
结果出来了
结果:10000
注意通道类型在单向与双向之间的转换规则,双向通道类型可以转换为单向通道类型。例如:
var ch1 chan float32 = make(chan float32, 0)
var ch2 <-chan float32 = ch1
var ch3 chan<- float32 = ch1
ch1
是双向通道类型,它既可以赋值给只接收数据的单向通道类型的变量,也可以赋值给只发送数据的单向通道类型的变量。
但是,只接收数据或者只发送数据的通道类型不能转换为双向通道类型。所以,下面代码会发生错误。
var ch4 <-chan int
var ch5 chan int = ch4
ch4
是单向通道类型的变量,ch5
为双向通道类型的变量,ch4
赋值给ch5
会发生错误。
21.2.4 通道与select
语句
select
语句跟switch
语句类似,都包含case
或default
子句。select
语句与通道一起使用,case
子句必须提供发送数据或者接收数据的操作。其格式如下:
select {
case ch <- n:……
case x <- ch :……
case <- ch :……
default:……
}
下面的示例演示了运用select
语句来生成范围为[1, 5]
的随机整数。
// 创建通道实例
var mych = make(chan int)
// 在新的协程上运行
go func() {select {case mych <- 1:case mych <- 2:case mych <- 3:case mych <- 4:case mych <- 5:}
}()
// 接收通道中的数据
n := <-mych
fmt.Printf("随机整数: %d\n", n)
上述代码中,首先调用make
函数创建一个无缓冲的通道实例,接着启动一个新的协程,在新协程的匿名函数中,使用select
语句和五个case
子句,子句中分别向通道发送数值1、2、3、4、5。五个case
子句只有一个会被执行,这个被执行的case
子句是由运行时随机选择的,而在main
协程中,<-mych
表达式接收的是被随机发送的值,于是便实现了产生随机整数的功能。
此示例的运行结果如下:
// 第一次运行
随机整数:2
// 第二次运行
随机整数:1
// 第三次运行
随机整数:5
结合通道和select
语句,也可以实现操作超时的功能。下面的示例实现一个简单的口算考试程序。程序运行后,随机生成两个100以内的整数值,然后要求用户口算出它们的和。用户需要在5秒钟内输入答题。
步骤1: 创建两个通道实例。
var (// 标志口算完毕chFinish = make(chan bool)// 标志已超时chTimeout = make(chan bool)
)
当用户输入口算结果,完成答题后,会发送数据到chFinish
通道;如果用户超出规定时间仍未完成答题,就会发送数据到chTimeout
通道。
步骤2: 定义一个常量,设定值为5,表示答题最长时间为5秒。
const maxSecond = 5
步骤3: 启动新的协程,处理生成口算题目以及用户答题逻辑。
go func() {rand.Seed(time.Now().UnixNano())// 产生100以内的随机整数a := rand.Intn(100)b := rand.Intn(100)var input int // 用户输入的计算结果r := a + b // 正确的计算结果fmt.Printf("题目: %d + %d =?\n", a, b)fmt.Print("请输入结果:")fmt.Scanln(&input)// 生成结果var cr bool = input == rchFinish <- cr
}()
步骤4: 再启动一个新的协程,负责计时,一旦超时,就会向chTimeout
通道发送数据。
go func() {time.Sleep(time.Second * maxSecond)chTimeout <- true
}()
步骤5: 在main
协程中,使用select
语句,并让各case
子句分别从前面创建的两个通道接收数据,最终给出考试结果。
select {
case res := <-chFinish:if res {fmt.Print("\n恭喜你,答对了\n")} else {fmt.Print("\n噢,答错了\n")}
case <-chTimeout:fmt.Print("\n很遗憾,时间到了\n")
}
步骤6: 当通道不再使用时,可将其关闭。
close(chFinish)
close(chTimeout)
如果用户能在规定的时间作答,就验证其输入的答案是否正确(由chFinish
通道中的值标识);如果用户超时未作答,则给出提示。
下面是三次运行该示例的结果。
// 正确作答
题目:9 + 33 =?
请输入结果:42
恭喜你,答对了// 答案不正确
题目:86 + 49 =?
请输入结果:112
噢,答错了// 超时未作答
题目:98 + 15 =?
请输入结果:
很遗憾,时间到了
21.3 互斥锁
当多个Go协程同时访问某一段代码时,会出现逻辑混乱的现象。举个例子,定义一个throw
函数,假设用于模拟抛球机工作。当小球的总数为0时,停止抛球。
func throw() {for {if Total < 1 {// 无球可抛时退出break}// 等待一下,抛球需要一定的时间time.Sleep(time.Millisecond * 300)Total --fmt.Printf("剩余 %d 个球\n", Total)}
}
在main
函数中启动四个新协程,表示四台抛球机在抛球,小球总数为20。
func main() {Total = 20for i := 0; i < 4; i++ {go throw()}// 暂停一下,等待其他协程完成time.Sleep(time.Second * 8)
}
然而,运行后会发现存在逻辑错误——剩余的小球总数会变为负数。
剩余18个球
剩余19个球
剩余17个球
剩余16个球
剩余15个球
剩余14个球
剩余13个球
剩余12个球
剩余11个球
剩余10个球
剩余9个球
剩余8个球
剩余7个球
剩余6个球
剩余5个球
剩余4个球
剩余3个球
剩余2个球
剩余1个球
剩余0个球
剩余-1个球
剩余-2个球
这是因为四个协程是相互独立的,它们同时执行throw
函数,当协程A判断还有剩余的球后,即将抛出一个球。正在此时协程B却把球抛出去了,而A根本不知道,于是它继续执行。也就是说,A并没有抛球,却把Total
减掉1。四个协程一起运行,这种情况会不断地发生,最终导致状态不统一,引发逻辑错误,就会出现剩余的小球总数为负数的结果。
要解决此问题,需要加一把 “锁” ,把抛一次球的整个过程锁定,只允许一个协程进行操作,其他协程“原地待命”。当这个协程抛完一次球,解除锁定,然后其他协程再去抛球。
接下来对上述例子进行修改,在throw
函数中加上互斥锁(sync
包公开的Mutex
类型)。
var locker = new(sync.Mutex)func throw() {for {// 此处开始上锁locker.Lock()if Total < 1 {// 无球可抛时退出break}// 等待一下,抛球需要一定的时间time.Sleep(time.Millisecond * 300)Total --fmt.Printf("剩余 %d 个球\n", Total)// 完成后要解锁locker.Unlock()}
}
互斥锁的锁定范围应覆盖从对Total
变量进行判断到将Total
变量减去1这个过程,在此过程中,始终只允许一个协程访问代码,防止Total
变量被意外更改。
Lock
方法与Unlock
方法的调用必须成对出现,即锁定资源后,要记得将其解锁,否则其他协程将永远无法访问资源。
经过修改后,就能得到正确的结果。
剩余19个球
剩余18个球
剩余17个球
剩余16个球
剩余15个球
剩余14个球
剩余13个球
剩余12个球
剩余11个球
剩余10个球
剩余9个球
剩余8个球
剩余7个球
剩余6个球
剩余5个球
剩余4个球
剩余3个球
剩余2个球
剩余1个球
剩余0个球
21.4 WaitGroup类型
sync.WaitGroup
类型内部维护一个计数器,某个Go协程调用Wait
方法后会被阻塞,直到WaitGroup
对象的计数器变为0。
调用Add
方法可以增加计数器的值,调用Done
方法会使计数器的值减1。实际上,Done
方法内部也调用了Add
方法,传递的参数值为-1。下面是Done
方法的源代码。
func (wg *WaitGroup) Done() {wg.Add(-1)
}
所以,调用Add
方法并向参数传递负值,也可以减少计数器的值。
在本章前面的各节中,有多个示例代码都会在main
函数结束之前调用time.Sleep
函数来让主协程暂停,用以等待其他协程执行完毕。就像下面这样:
func main() {……// 暂停一下,等待其他协程完成time.Sleep(time.Second * 8)
}
使用本节所介绍的WaitGroup
类型就不需要用Sleep
函数来暂停了,只要在主协程上调用其Wait
方法,主协程就会阻塞并且等到计数器为0时才会继续运行。
下面代码演示执行三个新的协程,计数器增加3,每个协程在执行完成时调用Done
方法让计数器减1。主协程上调用Wait
方法后会一直处于等待状态,直到三个协程都顺利完成。
func main() {var wg sync.WaitGroup// 增加计数器wg.Add(3)for i := 1; i <= 3; i++ {go func(n int) {// 执行完成时将计数器减1defer wg.Done()fmt.Printf("开始执行第 %d 个协程\n", n)time.Sleep(time.Second * 2)fmt.Printf("第 %d 个协程执行完毕\n", n)}(i)}// 等待上述各协程执行完成wg.Wait()fmt.Println("所有协程已完成")
}
运行结果如下:
开始执行第3个协程
开始执行第1个协程
开始执行第2个协程
第1个协程执行完毕
第2个协程执行完毕
第3个协程执行完毕
所有协程已完成
【思考】
- 如何创建双向通道?
- 在函数调用时加上 go 关键字有何作用?
- WaitGroup 是如何增加和减少任务数量的?
如何创建双向通道?
在Go语言里,双向通道能够进行数据的发送与接收操作。创建双向通道可以借助make
函数来实现,具体的语法格式如下:
channel := make(chan Type) // 无缓冲通道
channel := make(chan Type, capacity) // 有缓冲通道
这里的Type
指的是通道能够存放的数据类型,capacity
代表通道的缓冲容量。若不指定capacity
,创建的就是无缓冲通道;若指定了capacity
,创建的则是有缓冲通道。
以下是创建无缓冲和有缓冲双向通道的示例代码:
package mainimport "fmt"func main() {// 创建无缓冲的双向通道,可存放int类型数据unbufferedChan := make(chan int)// 创建有缓冲的双向通道,可存放int类型数据,缓冲容量为2bufferedChan := make(chan int, 2)fmt.Printf("无缓冲通道类型: %T\n", unbufferedChan)fmt.Printf("有缓冲通道类型: %T\n", bufferedChan)
}
在上述代码中,unbufferedChan
是无缓冲的双向通道,bufferedChan
是有缓冲的双向通道,二者都可以存放int
类型的数据。
在函数调用时加上go
关键字有何作用?
在函数调用时加上go
关键字,能够启动一个新的Go协程来异步执行该函数。协程是一种轻量级的线程,由Go运行时系统进行调度。借助go
关键字启动的协程会和主协程(也就是执行main
函数的协程)并行执行。
下面是一个简单的示例代码:
package mainimport ("fmt""time"
)func printNumbers() {for i := 1; i <= 5; i++ {fmt.Println(i)time.Sleep(time.Second)}
}func main() {// 启动一个新的协程来执行printNumbers函数go printNumbers()// 主协程继续执行fmt.Println("主协程继续执行...")time.Sleep(6 * time.Second)fmt.Println("主协程结束")
}
在上述代码中,go printNumbers()
启动了一个新的协程来执行printNumbers
函数,主协程不会等待该协程执行完毕,而是继续执行后续的代码。
WaitGroup是如何增加和减少任务数量的?
sync.WaitGroup
属于Go语言标准库中的一个类型,其作用是等待一组协程执行完毕。它内部维护着一个计数器,该计数器的数值表示尚未完成的协程数量。
Add(delta int)
:此方法用于增加计数器的值,delta
代表要增加的数量。通常在启动协程之前调用该方法,以告知WaitGroup
有多少个协程需要等待。Done()
:该方法用于减少计数器的值,实际上它调用了Add(-1)
。一般在协程执行完毕时调用此方法。Wait()
:调用此方法会使当前协程阻塞,直到计数器的值变为0,也就是所有协程都执行完毕。
以下是一个使用WaitGroup
的示例代码:
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // 协程执行完毕,计数器减1fmt.Printf("Worker %d starting\n", id)time.Sleep(time.Second)fmt.Printf("Worker %d done\n", id)
}func main() {var wg sync.WaitGroup// 启动3个协程,计数器加3wg.Add(3)for i := 1; i <= 3; i++ {go worker(i, &wg)}// 等待所有协程执行完毕wg.Wait()fmt.Println("All workers done")
}
在上述代码中,wg.Add(3)
把计数器的值设为3,代表有3个协程需要等待。每个协程在执行完毕时会调用wg.Done()
让计数器减1。wg.Wait()
会使主协程阻塞,直到计数器的值变为0,也就是所有协程都执行完毕。
相关文章:
Go语言入门基础:协程
第21章 协程 目录 21.1 启动Go协程 协程的概念与特点启动协程的方法及示例协程执行顺序的控制 21.2 通道 通道类型及表示方式21.2.1 实例化通道21.2.2 数据缓冲21.2.3 单向通道21.2.4 通道与select语句 21.3 互斥锁 协程并发访问的逻辑问题互斥锁的使用示例 21.4 WaitGroup类…...
Servlet+tomcat
serverlet 定义:是一个接口,定义了java类被浏览器(tomcat识别)的规则 所以我们需要自定义一个类,实现severlet接口复写方法 通过配置类实现路径和servlet的对应关系 执行原理 当用户在浏览器输入路径,会…...
中间件和组件
文章目录 1. 前言2. 中间件介绍3. 组件介绍4. 区别对比5. 简单类比6. 总结 中间件和组件 1. 前言 中间件和组件是软件开发中两个重要的概念,但它们的定位和作用完全不同。中间件解决的事通信、跨系统、安全等问题,组件是解决具体业务模块,提高…...
piccolo-large-zh-v2 和 bge-m3哪个效果好?
环境: piccolo-large-zh-v2 bge-m3 问题描述: piccolo-large-zh-v2 和 bge-m3哪个效果好? 解决方案: 比较Piccolo-large-zh-v2(商汤)与BGE-M3(智源)的效果时,需结合…...
《告别试错式开发:TDD的精准质量锻造术》
深度解锁TDD:应用开发的创新密钥 在应用开发的复杂版图中,如何雕琢出高质量、高可靠性的应用,始终是开发者们不懈探索的核心命题。测试驱动开发(TDD),作为一种颠覆性的开发理念与方法,正逐渐成…...
哈希函数详解(SHA-2系列、SHA-3系列、SM3国密)案例:构建简单的区块链——密码学基础
文章目录 一、密码哈希函数概述1.1 哈希函数的基本概念1.2 哈希函数在数据安全中的应用 二、SHA-2系列算法详解2.1 SHA-2的起源与发展2.2 SHA-256技术细节与实现2.3 SHA-384和SHA-512的特点2.4 SHA-2系列算法的安全性评估 三、SHA-3系列算法详解3.1 SHA-3的起源与设计理念3.2 K…...
CUDA输出“hello world”
在我们学习任何一门编程语言的时候, 无疑当我们真正用其输出“hello world”的时候, 我们已经成功入门, 接下来要做的就是从入门到放弃了😆 接下来我们通过对比C和CUDA来学习CUDA的运行逻辑: C中的hello worldCUDA中的hello world文本编辑器编写源代码, 比如vscod…...
计算机视觉与深度学习 | 视觉里程计算法综述(传统+深度)
视觉里程计算法综述 1. 算法分类与原理1.1 传统几何方法1.2 深度学习方法2. 关键公式与模型2.1 本征矩阵分解2.2 深度学习模型架构3. 代码实现与开源项目3.1 传统方法实现3.2 深度学习方法实现4. 挑战与未来方向总结传统视觉里程计算法综述1. 算法分类与核心原理1.1 特征点法1.…...
c++ 函数参数传递
C 中的值传递和地址传递 在 C 中,函数参数传递主要有两种方式:值传递和地址传递(指针传递和引用传递都属于地址传递的变体)。 1. 值传递 特点 函数接收的是实参的副本对形参的修改不会影响原始变量适用于小型数据(…...
计算机视觉与深度学习 | 什么是图像金字塔?
图像金字塔详解 图像金字塔 图像金字塔详解1. **定义**2. **原理与公式****2.1 高斯金字塔****2.2 拉普拉斯金字塔**3. **代码示例****3.1 使用OpenCV实现****3.2 手动实现高斯模糊与降采样**4. **应用场景**5. **关键点总结**1. 定义 图像金字塔是一种多尺度图像表示方法,将…...
AI超级智能体教程(五)---自定义advisor扩展+结构化json输出
文章目录 1.自定义拦截器1.2自定义Advisor1.2打断点调试过程1.3Re-reading Advisor自定义实现 2.恋爱报告开发--json结构化输出2.1原理介绍2.1代码实现2.3编写测试用例2.4结构化输出效果 1.自定义拦截器 1.2自定义Advisor spring里面的这个默认的是SimpleloggerAdvisor&#…...
ActiveMQ 集群搭建与高可用方案设计(一)
一、引言 在当今分布式系统盛行的时代,消息中间件扮演着至关重要的角色,而 ActiveMQ 作为一款开源的、功能强大的消息中间件,在众多项目中得到了广泛应用。它支持多种消息传输协议,如 JMS、AMQP、MQTT 等 ,能够方便地实…...
MySQL数据操作全攻略:DML增删改与DQL高级查询实战指南
知识点4【MySQL的DDL】 DDL:主要管理数据库、表、列等操作。 库→表(二维)→列(一维) 数据表的第一行是 列名称 数据库是由一张或多张表组成 我们先学习在数据库中创建数据表 0、常见的数据类型: 1、…...
RabbitMQ 中的六大工作模式介绍与使用
文章目录 简单队列(Simple Queue)模式配置类定义消费者定义发送消息测试消费 工作队列(Work Queues)模式配置类定义消费者定义发送消息测试消费负载均衡调优 发布/订阅(Publish/Subscribe)模式配置类定义消…...
一种基于重建前检测的实孔径雷达实时角超分辨方法——论文阅读
一种基于重建前检测的实孔径雷达实时角超分辨方法 1. 专利的研究目标与实际问题意义2. 专利提出的新方法、模型与公式2.1 重建前检测(DBR)与数据裁剪2.1.1 回波模型与检测准则2.1.2 数据裁剪效果2.2 数据自适应迭代更新2.2.1 代价函数与迭代公式2.2.2 矩阵递归更新2.3 正则化…...
代购平台如何“说本地话,穿本地衣”
当海外消费者点开一个代购商城,看到满屏机械翻译的中式文案,他们大概率会默默关闭页面。还有数据显示,不符合本地审美的页面设计会导致70%的潜在客户流失,而专业的本地化改造能让订单转化率提升3倍以上。 01 AI翻译:让…...
C++调试(叁):编译qBreakpad并使用其生成Dump文件
目录 1.前言 2.生成Dump文件的第三方库 3.第三方库下载链接 4.编译qBreakpad 5.VS中使用qBreakpad 6.qBreakpad测试程序 前言 在第二篇文章中,我主要讲解了如何使用SetUnhandledExceptionFilter函数设置程序的异常回调,在设置的回调函数中调用MiniDumpWr…...
0基础 | STM32 | TB6612电机驱动使用
TB6612介绍及使用 单片机通过驱动板连接至电机 原因:单品机I/O口输出电流I小 驱动板:从外部引入高电压,控制电机驱动 电源部分 VM:电机驱动电源输入,输入电压范围建议为3.7~12V GND:逻辑电…...
Cycleresearcher:通过自动化评审改进自动化研究
1、引言 迄今为止,整个科学发现过程自动化的挑战在很大程度上仍未解决,特别是在生成和改进符合同行评审工作高标准的研究成果方面。此外,很少有工作涉及迭代反馈的整合,这对保持学术的健全性和新奇至关重要。当前的模型往往难以适…...
深入理解Redis SDS:高性能字符串的终极设计指南
📍 文章提示 10分钟掌握Redis核心字符串设计 | 从底层结构到源码实现,揭秘SDS如何解决C字符串七大缺陷,通过20手绘图示与可运行的C代码案例,助你彻底理解二进制安全、自动扩容等核心机制,文末附实战优化技巧ÿ…...
【tcp连接windows redis】
tcp连接windows redis 修改redis.conf 修改redis.conf bind * -::*表示禁用保护模式,允许外部网络连接 protected-mode no...
【AI平台】n8n入门6:调用MCP服务(非社区节点)
前言 前边用n8n搭建一个MCP服务,现在,用n8n调用其他服务商提供的MCP服务。本文以高德地图服务为例,记录一下操作过程。 实现案例功能 MCP是啥 MCP(Model Context Protocol,模型上下文协议)是由Anthropi…...
C++负载均衡远程调用学习之 Dns-Route关系构建
目录 1.LARS-DNS-MYSQL环境搭建 2.LARSDNS-系统整体模块的简单说明 3.Lars-Dns-功能说明 4.Lars-Dns-数据表的创建 5.Lars-Dns-整体功能说明 6.Lars-DnsV0.1-Route类的单例实现 7.Lars-DnsV0.1-Route类的链接数据库方法实现 8.Lars-DnsV0.1-定义存放RouteData关系的map数…...
Linux53 百度网盘运行(下载devtoolset11后仍提示stdc++3.0.29缺失 计划用docker容器隔离运行,计划后续再看)
算了 放弃 都用到docker了 计划先看看系统服务后续再研究吧 百度网盘运行(下载devtoolset11后仍提示stdc3.0.29缺失 计划用docker容器隔离运行 但是由于系统服务未扎实,计划后续再看 重新下了el7的版本 刚才已启动成功 单输入xlock不启动 切换用户也不启动 …...
ASP.NET MVC 入门与提高指南八
45. 神经形态计算与 MVC 应用性能革新 45.1 神经形态计算概念 神经形态计算是借鉴生物神经系统的结构和工作原理来设计计算系统。它模仿人脑神经元和突触的工作方式,具备低功耗、高并行性和自适应学习等特性,能在处理复杂感知和认知任务时展现出卓越的…...
Python刷题:流程控制(下)
今天刷的是PythonTip的Python 入门挑战中的题,整体难度不高,适合小白练手以及巩固知识点。下面会进行详细讲解。 每日一句 在无人问津的角落里, 默默努力的人独自发光, 孤独是他们奋斗路上的常客, 却也是成就他们的…...
【Bootstrap V4系列】学习入门教程之 组件-徽章(Badge)和面包屑导航(Breadcrumb)
Bootstrap V4系列 学习入门教程之 组件-徽章(Badge)和面包屑导航(Breadcrumb) 徽章(Badge)一、示例二、根据情境改变外观三、胶囊式徽章(Pill badges)四、链接 面包屑导航࿰…...
结合强化学习RL和SFT各自训练优势,让模型边学边练,从而平衡Zero-RL训练中的模仿和探索!!
摘要:最近在大型推理模型(LRMs)方面的进展表明,通过简单的基于规则的奖励进行强化学习(RL),可以涌现出复杂的行为,例如多步推理和自我反思。然而,现有的零强化学习&#…...
ai之paddleOCR 识别PDF python312和paddle版本冲突 GLIBCXX_3.4.30
这里写自定义目录标题 问题一**解决方案****方法 1:使用符号链接将系统库链接到 Conda 环境** **补充说明****验证修复结果** 问题二:**问题根源****解决方案****1. 确认 TensorRT 安装状态****2. 安装 TensorRT 并配置环境变量****3. 验证 TensorRT 与 …...
C++ 单例模式详解
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。 概念解析 单例模式的核心思想是: 限制类的实例化次数,确保在整个应用程序中只有一个实例存在 提供对该实例的全局访问点 控制共享…...
生成器模式(Builder Pattern)
好问题!生成器模式(Builder Pattern)在现实生活和程序开发中非常常见,它适合创建**“一步一步搭建起来的复杂对象”**。 🧠 一句话定义 生成器模式(Builder Pattern)是一种将复杂对象的构建过程…...
计算机网络八股文--day4 --传输层TCP与UDP
这是面试中最常考到的一层:端到端(也就是进程之间)的透明数据传输服务,差错控制和流量控制 该层呈上启下,像上面的资源子网提高服务,并使用下面通信子网的服务 端口,用于唯一标识主机上进程的&…...
大型语言模型个性化助手实现
大型语言模型个性化助手实现 目录 大型语言模型个性化助手实现PERSONAMEM,以及用户资料和对话模拟管道7种原位用户查询类型关于大语言模型个性化能力评估的研究大型语言模型(LLMs)已经成为用户在各种任务中的个性化助手,从提供写作支持到提供量身定制的建议或咨询。随着时间…...
步进电机中断函数解释
STM32 motor111.c 中 HAL_TIM_PeriodElapsedCallback 函数逐行解释 下面我们对 STM32 项目中 motor111.c 文件里的 HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 函数进行逐行解析,帮助初学者理解每一行代码的作用。此函数是在定时器产生更新中断时被调…...
多把锁以及线程死锁问题
在 Java 中,每一个对象都可以作为一把锁,synchronized 通过获取对象头中的锁标志位来实现同步。当一个线程获取到对象的锁后,其他线程就无法再获取该锁,只能等待获取到锁的线程释放锁之后才能继续执行被 synchronized 修饰的代码块…...
Vue 3 Element Plus 浏览器使用例子
Element Plus 是一个基于 Vue 3 的流行开源 UI 库,提供了一系列的组件,帮助开发者快速构建现代化的用户界面。它的设计简洁、现代,包含了许多可定制的组件,如按钮、表格、表单、对话框等,适合用于开发各种 Web 应用。 …...
NoxLucky:个性化动态桌面,打造独一无二的手机体验
在数字时代,手机桌面的个性化设置已经成为许多人表达自我和展示个性的重要方式。今天,我们要介绍的 NoxLucky,就是这样一款功能强大的动态桌面手机应用。它不仅提供了独家的动态壁纸素材库,还支持将抖音、INS等平台的短视频直接设…...
如何在WORD WPS中输入英语音标 批量给英语标注音标
在我国,英语的学习,已经是贯穿小学到大学的课程,英语学习,关键是听说读写,而听说读,都离不开一个字--音,这就涉及到了英语的音标了。音标(Phonetics)是表示单词发音的一种…...
RUST变量学习笔记
1,作用域概念 捕获环境 2,所有权概念 Rust则是通过所有权和借用来保证内存安全。很多人不理解为啥说Rust是内存安全的,其实就是在默认情况下,你是写不出内存不安全的代码的。 Rust的所有权并不难理解,它有且只有如下…...
n8n工作流自动化平台的实操:本地化高级部署
一、本地高级部署 1.下载 docker pull docker.n8n.io/n8nio/n8n 2.运行 docker volume create n8n_data docker run -dit --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n -e N8N_SECURE_COOKIEfalse -e N8N_RUNNERS_ENABLEDtrue -e N8N_ENFORCE_SETTINGS_FIL…...
【Ansible自动化运维实战:从Playbook到负载均衡指南】
本文是「VagrantVirtualBox虚拟化环境搭建」的续篇,深入探索Ansible在自动化运维中的核心应用: ✅ Ansible核心技能:Playbook编写、角色(Roles)模块化、标签(Tags)精准控制 ✅ 实战场景覆盖&a…...
数据赋能(210)——质量管理——可靠性原则
概述 数据可靠性原则确保数据的准确性、完整性、一致性和可信性,是决策和业务活动对数据质量的基本要求。在信息化和数字化快速发展的今天,数据已成为企业的重要资产,数据可靠性直接影响到企业的决策质量和业务活动效果。数据可靠性是数据质…...
二、机器学习中Python变量基础
二、Python变量基础 像C语言和Matlab一样,变量名由字母、数字、下划线组成(但不能以数字开头,字母区分大小写)变量名不能与内置的函数同名。 根据变量是否可以充当容器,将变量类型分为基本类型和高级类型。 基本变量…...
有机玻璃材质数据采集活性炭吸附气体中二氧化硫实验装置
JGQ112Ⅱ有机玻璃材质数据采集活性炭吸附气体中二氧化硫实验装置 一.实验目的 1.熟悉活性炭吸附剂的特性和在SO2气体净化方面的应用。 2.掌握活性炭吸附法的流程和实验过程中各参数的控制方法。 3.了解主要参数变化对吸附效率的影响。 4.掌握吸附等温线概念和测定方法。 二.技术…...
Javase 基础入门 —— 07 接口
本系列为笔者学习Javase的课堂笔记,视频资源为B站黑马程序员出品的《黑马程序员JavaAI智能辅助编程全套视频教程,java零基础入门到大牛一套通关》,章节分布参考视频教程,为同样学习Javase系列课程的同学们提供参考。 01 概述 接…...
LangChain:重构大语言模型应用开发的范式革命
2022年10月22日,Harrison Chase在GitHub上提交了名为LangChain的开源项目的第一个代码版本。这个看似普通的代码提交,却悄然开启了一场重塑大语言模型(LLM)应用开发范式的技术革命。彼时,距离ChatGPT引爆全球人工智能浪…...
【现代深度学习技术】现代循环神经网络04:双向循环神经网络
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重…...
重塑数学边界:人工智能如何引领数学研究的新纪元
目录 一、人工智能如何重新定义数学研究的边界 (一)数学与AI的关系:从基础理论到创新思维的回馈 (二)AI的创造力:突破传统推理的局限 (三)AI对数学研究的潜在贡献:创…...
链表的回文结构题解
首先阅读题目: 1.要保证是回文结构 2.他的时间复杂度为O(n)、空间复杂度为O(1) 给出思路: 1.首先利用一个函数找到中间节点 2.利用一个函数逆置中间节点往后的所有节点 3.现在有两个链表,第一个链表取头节点一直到中间节点、第二个链表取头结点到尾…...
xLua笔记
Generate Code干了什么 肉眼可见的,在Asset文件夹生成了XLua/Gen文件夹,里面有一些脚本。然后对加了[CSharpCallLua]的变量寻找引用,发现它被XLua/Gen/DelegatesGensBridge引用了。也可以在这里查哪些类型加了[CSharpCallLua]。 public over…...