GO协程(Goroutine)问题总结

在使用Go语言来编写代码时,遇到的一些问题总结一下
[参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html

1. main()函数默认的Goroutine

场景再现:

今天在看到这个教程的时候,在自己的电脑上跑了一下示例的代码。
发现了描述与我的执行结果不同的地方,官方文档描述如下:

这一次的执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。

但是我执行后的情况是,如图:
 main()函数默认的Goroutine

可以看到,我最终的执行结果是都输出了,而不是只输出了

main goroutine done!
Why?

原因是——虽然 main() 函数中调用了 go hello(),主 goroutine 在打印完 main goroutine done! 后就会退出,但:

在主 goroutine 退出前,如果新启动的 goroutine 有足够的时间运行完,Hello Goroutine! 就会输出。
上面这段代码启动了一个新 goroutine,但程序的执行是并发的,不是同步/阻塞的。

执行流程是:

go hello() 启动了一个新 goroutine;fmt.Println("main goroutine done!") 被执行;如果此时 main() 返回前,新 goroutine 还没来得及执行完,那它也会被强行终止;但如果它已经执行完了,就能看到打印的内容。

这两句都成功输出,是因为你的电脑配置比较好,执行速度非常快,新启动的 goroutine 来得及在 main() 退出前完成打印。

正确做法:用 sync.WaitGroup 或 time.Sleep

2 . Go 协程(Goroutine)的两个关键点

协程不能保证执行的顺序,但是如果加了time.sleep的话,可以保障协程执行完毕

✅ Go 协程(Goroutine)的两个关键点:

①. 协程是并发的,不能保证执行顺序

go hello() 启动后,什么时候运行是由 Go 调度器决定的。
主协程和子协程是“谁抢到 CPU 谁先跑”,谁先打印是不确定的。

所以:

go hello()
fmt.Println("main done")

有可能先打印 main done,也可能先打印 Hello,取决于当时调度情况。

② 加 time.Sleep() 可以“间接保障”子协程执行完

加 time.Sleep() 相当于强行让主协程等一下,给子协程留时间执行完。

所以子协程通常会有时间执行完,看起来“像是被保障了执行”。
❗但注意:time.Sleep() ≠ 可靠同步
虽然 time.Sleep() 很简单,但它存在几个问题:

问题点说明
❌ 不精准你不知道子协程到底需要多少时间,sleep 多了浪费,少了又执行不完
❌ 不可扩展如果你有多个协程,就很难 sleep 到合适的时间
✅ 适合临时调试用于演示或实验是可以的

✅ 正确做法:用 sync.WaitGroup


import ("fmt""sync"
)func hello(wg *sync.WaitGroup) {fmt.Println("Hello Goroutine!")wg.Done() // 协程结束,通知 WaitGroup
}func main() {var wg sync.WaitGroupwg.Add(1)        // 告诉 WaitGroup 等待 1 个协程go hello(&wg)    // 启动协程wg.Wait()        // 等待所有协程结束fmt.Println("main goroutine done!")
}

这样就能准确地等待协程执行完再退出,不用靠 sleep。

总结一句话:
time.Sleep() 是简单粗暴的等待方式,可以在小程序中“凑合用”,但真正写程序,用 sync.WaitGroup 等同步机制更稳、更准、更专业。

3.defer

3.1 defer使用对比
func hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)
}func hello(i int){fmt.Println("Hello Goroutine!",i)wg.Done()
}
//在 正常情况下的效果是一样的:都会确保在 goroutine 执行完成后调用 wg.Done(),
//从而通知 WaitGroup,减少一个等待计数。

✅ defer wg.Done() 的优势:
defer 会在 函数返回前自动执行,即使函数中间发生了 panic(未被恢复),defer 也会运行(前提是没有让程序直接崩溃)。

这意味着:

func hello(i int) {defer wg.Done()// 如果这里出现错误,也能保证 Done 会执行fmt.Println("Hello Goroutine!", i)
}

更安全、稳健,防止遗漏。

❗ 手动调用 wg.Done() 有风险:

func hello(i int) {fmt.Println("Hello Goroutine!", i)wg.Done()
}

如果你写了更复杂的逻辑,中途 return 或 panic 了,wg.Done() 可能根本执行不到,就会导致 wg.Wait() 永远卡住。
✅ 总结:

写法是否推荐原因
defer wg.Done()✅ 推荐更安全,即使中途出错也能保证 Done 被调用
手动调用 wg.Done()⚠️ 慎用必须确保函数末尾一定能执行到,否则容易漏调用

所以你的判断是对的:“两种写法是一样的”,功能上是对的;但为了避免未来的问题,推荐使用 defer 写法,更健壮、易维护。

3.2defer的作用、在Goroutine中的使用案例以及执行顺序
3.2.1 defer的作用

✅ defer 的作用
defer 用于延迟一个函数的执行,直到外围函数(即当前函数)返回之前才调用。

换句话说:

无论当前函数中发生了什么(正常结束或提前 return),defer 注册的语句都会在函数结束前自动执行。

📌 举个例子说明:

func demo() {fmt.Println("start")defer fmt.Println("this is defer")fmt.Println("end")
}

输出结果是:

start
end
this is defer

defer fmt.Println(“this is defer”) 被延迟执行到 demo() 函数退出前的最后一刻。

✅ defer 的常见用途

用途示例说明
释放资源defer file.Close()防止文件忘记关闭
解锁defer mu.Unlock()防止死锁
记录日志/退出操作defer log.Println("退出")确保函数末尾执行
Done 通知defer wg.Done()保证 goroutine 退出时减少计数
3.2.2 在Goroutine中的使用案例
package mainimport ("fmt""sync"
)var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // 必须在协程结束时调用 wg.Done() 通知 WaitGroup 协程结束fmt.Println("Hello Goroutine!", i)
}
func main() {for i := 0; i < 10; i++ {wg.Add(1)   // 告诉 WaitGroup 等待 1 个协程go hello(i) // 启动另外一个goroutine去执行hello函数}wg.Wait() // 等待所有的 goroutine 结束fmt.Println("main goroutine done!")
}
3.2.3 多个defer的执行顺序

defer 在 Go 中的执行顺序就是栈结构的“先进后出(LIFO)”原则:

🧠 defer 的执行顺序:后注册,先执行
也就是:

func demo() {defer fmt.Println("1")defer fmt.Println("2")defer fmt.Println("3")
}

🧾 输出结果:

3
2
1

📦 原理解释:
当你写多个 defer 时,Go 会把它们压入一个栈中,函数结束时就从栈顶往下依次执行这些 defer。

行号执行内容进入 defer 栈
1defer fmt.Println("1")栈:[1]
2defer fmt.Println("2")栈:[1, 2]
3defer fmt.Println("3")栈:[1, 2, 3]
return前执行 defer从栈顶开始弹出输出顺序:3 → 2 → 1

✅ 常见用途
多个 defer 通常用于按顺序清理资源,比如:

func handleFile() {f1, _ := os.Open("file1.txt")defer f1.Close() // 最后关闭f2, _ := os.Open("file2.txt")defer f2.Close() // 比 f1 先关闭fmt.Println("doing something...")
}

结果是 file2 会先关闭,然后 file1 再关闭 —— 和你打开文件的顺序是相反的,这正是我们想要的行为(像“关门要倒着来”一样)。

✅ 总结:

特性说明
顺序多个 defer 是“后注册,先执行”(LIFO)
应用场景清理资源(文件、锁、连接等)、错误处理等
安全性即使函数 returnpanic,也会执行

4.主协程和其他协程的关系,主协退出了,其他的协程还执行吗?

代码使用了官网提供的:

package mainimport ("fmt""time"
)func main() {// 合起来写go func() {i := 0for {i++fmt.Printf("new goroutine: i = %d\n", i)time.Sleep(time.Second)}}()i := 0for {i++fmt.Printf("main goroutine: i = %d\n", i)time.Sleep(time.Second)if i == 2 {break}}
}

执行结果:

main goroutine: i = 1
new goroutine: i = 1
main goroutine: i = 2
new goroutine: i = 2
new goroutine: i = 3Process finished with the exit code 0

证明了主协程结束,其他线程不会再执行

5. java/c/c++线程与go协程的对比(与OS线程)

特性Java / C 的线程(OS Thread)Go 的 goroutine
线程类型操作系统线程(内核线程)用户级线程(协程)
线程模型1:1 模型M:N 模型
调度者操作系统Go 自带的调度器(runtime)
映射关系每个语言线程对应一个 OS 线程多个 goroutine 映射到多个 OS 线程
栈内存初始大小通常 1MB~2MB(固定)起始约 2KB(可动态伸缩)
创建成本高(需要系统调用)极低(用户态,几乎无开销)
调度成本高(内核态线程切换)低(用户态线程切换)
并发数量限制一般几千个十万甚至百万级
适合场景计算密集、高性能场景高并发、大量 I/O 场景
常用语言APIstd::thread, Threadgo myFunc()
内存使用效率相对较低非常高

🔍 示例类比:

类比Java / C 的线程Go 的 goroutine
比喻重型卡车:开销大但能干活自行车大军:轻量且灵活
调度员操作系统Go 自己的调度器
数量几千个已很吃力十万个都轻轻松松

✅ 图示说明

Java / C         =>        1:1 线程模型
┌──────────┐         ┌──────────┐
│ Thread A │───────▶│  OS 线程 A │
│ Thread B │───────▶│  OS 线程 B │
└──────────┘         └──────────┘Go              =>        M:N 线程模型
┌──────────────┐
│ goroutine 1  │
│ goroutine 2  │
│ goroutine 3  │──┐
│ goroutine 4  │  │
│ goroutine 5  │  ├──▶ 被 Go runtime 调度
│ goroutine N  │──┘     分配到 OS 线程 A/B/C…
└──────────────┘

✅ 总结一句话:
Java 和 C 的线程就是系统线程(1:1),重量级。
Go 的 goroutine 是用户级线程,轻量可扩展(M:N),适合高并发。

6.并发与并行

6.1并发 vs 并行 示意图
+-----------------------+         +-------------------------+
|       单核 CPU        |         |        多核 CPU          |
| (1 个核心处理器核)     |         |   (多个核心同时工作)      |
+-----------------------+         +-------------------------+
|                       |         |                         |
|  ┌───────────────┐    |         |  ┌───────────────┐      |
|  │   核心 CPU 核  │    |         |  │  核心 CPU 核 1 │      |
|  │               │    |         |  ├───────────────┤      |
|  │ 任务1          │    |         |  │ 任务1 (并行执行)│      |
|  │任务2 (并发切换) │ ←──┐      |  │任务2 (并行执行)│      |
|  └───────────────┘    |  |      |  └───────────────┘      |
|  任务1和任务2“交替”执行 |  |      |  多个任务“真正同时”执行   |
|                       |  |      |                         |
+-----------------------+  |      +-------------------------+|并发(Concurrency) |      并行(Parallelism)任务切换看似同时进行 |      物理上真正同时执行
6.2Go 的 G-P-M 调度模型简化示意
+----------------------------+
|           G (Goroutine)    |  // 轻量级任务
+----------------------------+│▼
+----------------------------+
|         P (Processor)       |  // 逻辑处理器(虚拟CPU核)
|  数量 = GOMAXPROCS 的值     |
+----------------------------+│▼
+----------------------------+
|       M (Machine/线程)      |  // 真实的 OS 线程
+----------------------------+│▼真正执行代码的 CPU 核心

你创建的很多 G (goroutine),由有限个 P(由 GOMAXPROCS 控制) 调度到 M(操作系统线程) 上执行。

当 GOMAXPROCS=1,只有一个 P,也就是说同一时刻只有一个 M 在执行 goroutine,表现为“并发”,但不是“并行”。

当 GOMAXPROCS>1,多个 P 和 M 允许 goroutine 并行运行在多个 CPU 核心上。

6.3举个例子

🍳 并发(Concurrency):
就像一个厨师(单核 CPU),轮流做多个菜(任务)。他可能:


先把锅烧热,然后去切菜,再回来翻炒,接着煮汤……

虽然这些菜好像都在“同时准备”,但其实是一个人来回切换工作状态完成的。

特点:


看起来同时,其实是轮流进行的本质上是 任务调度,而不是并行执行

👨‍🍳👨‍🍳 并行(Parallelism):
多个厨师(多核 CPU)在多个灶台上,真正同时做多个菜(任务):

一人炒菜,一人煮汤,一人蒸饭……

特点:

真正同时执行每个任务由不同核心独立运行

💡 套用到 Go 的场景里:

go func() 就像是安排一个菜让厨师做runtime.GOMAXPROCS(1) 就是告诉厨房:“我只配一个厨师”(不管你有几个灶台)Go 的调度器就像厨房总管,会安排炒什么菜、先做哪个、后做哪个

7. 缓冲信道和无缓冲信道

🌟 一张表格对比
特性无缓冲通道 (make(chan T))带缓冲通道 (make(chan T, N))
是否有缓冲区❌ 没有,容量为 0✅ 有,容量为 N(N ≥ 1)
是否阻塞发送方✅ 会,直到接收方准备好❌ 不会,只要缓冲区没满
是否阻塞接收方✅ 会,直到发送方发送❌ 不会,只要缓冲区有数据
典型应用同步通信,控制执行时机异步通信,解耦发送与接收
性能慢,但同步、控制精准快,但可能增加复杂性
死锁风险高(若不成对出现)低(发送和接收可以脱钩)
是否 FIFO✅ 是✅ 是
✅ 示例对比:无缓冲通道

package mainimport "fmt"func main() {ch := make(chan int) // 无缓冲通道go func() {val := <-chfmt.Println("接收成功:", val)}()ch <- 10              // 阻塞直到接收方准备好fmt.Println("发送成功")
}

执行顺序:

接收成功: 10
发送成功

👉 注意:如果你把 发送(ch <- 10) 放在 go 启动接收协程 之前,就会死锁!

✅ 示例对比:带缓冲通道
package mainimport "fmt"func main() {ch := make(chan int, 1) // 带缓冲通道,容量为1ch <- 10                // ✅ 不阻塞fmt.Println("发送成功")go func() {val := <-chfmt.Println("接收成功:", val)}()
}

执行顺序:

发送成功
接收成功: 10

👉 注意:即使接收方还没准备好,发送也不会阻塞(只要缓冲区有空间)。

🧠 总结一句话:
无缓冲通道:必须“手递手”,发送和接收必须同步发生。

带缓冲通道:允许“放下再走”,先发送存储,之后再接收。

🤔我的理解【ASK ChatGPT】
我的理解

无论是先发送还是先接收,都不应该主程序阻塞后续的程序,否则接收不到,或者发送不了。
无论先发送还是接收,都应该在下一步前建立一个协程来进行类似监听的协程来阻塞协程这样才能保证发送接收同步进行吗?
无论是发送(val<-10),还是接收(<-val)都会阻塞当前的协程,我说的对吗

GPT回答

✅ 你的原话分析:

❓“无论是先发送还是先接收,都不应该主程序阻塞后续的程序,否则接收不到,或者发送不了。”
✅ 完全正确!

如果你在 main() 主线程中直接执行 val := <-ch 或 ch <- 10,主 goroutine 会阻塞,导致之后的 goroutine 根本不会被调度运行。

所以要让 发送/接收那一方跑在 goroutine 中,才能让另一方执行并完成通信。

❓“无论先发送还是接收,都应该在下一步前建立一个协程来进行类似监听的协程来阻塞协程这样才能保证发送接收同步进行吗?”
✅ 非常正确,表达也很清晰。

Go 的无缓冲通道通信是 两个 goroutine 的同步配合行为。

不论谁先运行,都必须有 另一个 goroutine 在等待,通信才能发生。

✅ 举个常用结构例子:

go func() {ch <- 10  // goroutine A 发送
}()go func() {val := <-ch  // goroutine B 接收
}()

两个 goroutine 各自阻塞在 send/recv 点,一旦两者都就绪,通信立刻完成。

❓“无论是发送(val <- 10),还是接收(<-val)都会阻塞当前的协程,我说的对吗?”
✅ 完全正确!

操作是否阻塞说明
ch <- 10✅ 会阻塞如果没有协程在接收,就阻塞
<-ch✅ 会阻塞如果没有协程在发送,就阻塞

✅ 只要通道是无缓冲的,这两个操作都必须等对方准备好才能继续。

✅ 最核心的一句话总结:
无缓冲通道中的发送和接收,必须发生在两个独立的 goroutine 中,才能顺利配对并完成同步。

8. channel关闭(close)

8.1 channel 关闭(close)≠ 销毁

Go 里没有显式的“销毁”channel操作,只有 关闭(close) 操作。

close(ch) 表示:

禁止再向通道写入数据(写入会 panic)可以继续读取通道中剩余的缓冲数据读取到空了之后,再读取会得到零值(对应类型的默认值)并返回 ok=false
8.2 关闭 channel 后为什么“只能读取一次”?
通道关闭后,只能读取到剩余的数据,读取完就没有了。你可以多次从关闭的 channel 读取数据,直到读完所有剩余数据。但是如果你用 for range ch 循环,循环会在读完所有数据后自动结束。

重点:
for range ch 循环 只能执行一次,因为通道中的数据只会被消费一次,且通道关闭后不再接收新数据。

8.3 不关闭 channel,能读多次吗?
如果 不关闭 channel,就不能用 for range ch 这种方式循环读取,因为它会阻塞等待新的数据,直到 channel 关闭。但你可以用手动读取(val, ok := <-ch)不断读数据,只要有人往里面写,就能持续接收。多个接收者从同一个 channel 读取是“抢占式”的,数据会被分配给其中一个,不能保证所有接收者都能读到相同数据。
8.4 总结
状态是否能写入是否能读取多次备注
channel 已关闭不允许(写会 panic)可以读取剩余数据,读完返回零值+ok=false只能消费剩余数据,不能写入
channel 未关闭允许可以多次读取(取决于写入情况)读取会阻塞等待数据
  1. 举个例子说明
package mainimport "fmt"func main() {ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch) // 关闭通道for i := 0; i < 5; i++ {val, ok := <-chfmt.Println(val, ok)}
}

输出:

1 true
2 true
3 true
0 false  // 通道关闭且无剩余数据,返回类型零值,ok=false
0 false

如果你想多次“重新读取”同一批数据,就需要把数据保存到 slice 或其他容器,而不是依赖 channel 本身,因为 channel 数据是一次性消费的。

9.匿名函数(内联函数)

官方文档的Goroutine池中提供了

package mainimport ("fmt""math/rand"
)type Job struct {// idId int// 需要计算的随机数RandNum int
}type Result struct {// 这里必须传对象实例job *Job// 求和sum int
}func main() {// 需要2个管道// 1.job管道jobChan := make(chan *Job, 128)// 2.结果管道resultChan := make(chan *Result, 128)// 3.创建工作池createPool(64, jobChan, resultChan)// 4.开个打印的协程go func(resultChan chan *Result) {// 遍历结果管道打印for result := range resultChan {fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,result.job.RandNum, result.sum)}}(resultChan) //①匿名函数var id int// 循环创建job,输入到管道for {id++// 生成随机数r_num := rand.Int()job := &Job{Id:      id,RandNum: r_num,}jobChan <- job}
}// 创建工作池
// 参数1:开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {// 根据开协程个数,去跑运行for i := 0; i < num; i++ {go func(jobChan chan *Job, resultChan chan *Result) {// 执行运算// 遍历job管道所有数据,进行相加for job := range jobChan {// 随机数接过来r_num := job.RandNum// 随机数每一位相加// 定义返回值var sum intfor r_num != 0 {tmp := r_num % 10sum += tmpr_num /= 10}// 想要的结果是Resultr := &Result{job: job,sum: sum,}//运算结果扔到管道resultChan <- r}}(jobChan, resultChan)//②匿名函数}
}

对②匿名函数解释

  • (jobChan, resultChan)createPool(num int, jobChan chan *Job, resultChan chan *Result)处对应的参数值,
  • 然后(jobChan, resultChan)又被当作参数传入了go func(jobChan chan *Job, resultChan chan *Result)

用匿名函数的好处是 不需要额外写一个函数名,逻辑上简洁一些。

10.select处理多种类型事件例子

常见用法举例
假设你有几个不同类型的事件通过不同通道传递,你可以写类似的代码:

package mainimport ("fmt""time"
)func eventA(ch chan string) {time.Sleep(time.Second * 1)ch <- "Event A occurred"
}func eventB(ch chan int) {time.Sleep(time.Second * 2)ch <- 42
}func eventC(ch chan bool) {time.Sleep(time.Second * 3)ch <- true
}func main() {chA := make(chan string)chB := make(chan int)chC := make(chan bool)go eventA(chA)go eventB(chB)go eventC(chC)for i := 0; i < 3; i++ {select {case msg := <-chA:fmt.Println("Received from chA:", msg)case val := <-chB:fmt.Println("Received from chB:", val)case flag := <-chC:fmt.Println("Received from chC:", flag)}}
}

解释

  • 你启动3个 goroutine,它们各自延迟不同时间后往对应通道写入不同类型的事件数据。

  • 主协程中用一个 select 监听这3个通道。

  • 每当任意一个通道有数据,就进入对应 case 执行,打印接收到的事件。

  • 循环3次保证3个事件都能被处理。

重点总结

  • select 可以监听任意多个通道。

  • 每个 case 可以处理不同类型的数据。

  • 只要某个通道准备好数据,select 就会立即触发对应的 case。

  • select 本身不是“处理多种类型数据”的结构,但结合不同通道的类型可以做到这一点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86509.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…

分布式锁实战:Redisson vs. Redis 原生指令的性能对比

分布式锁实战&#xff1a;Redisson vs. Redis 原生指令的性能对比 引言 在DIY主题模板系统中&#xff0c;用户可自定义聊天室的背景、图标、动画等元素。当多个运营人员或用户同时修改同一模板时&#xff0c;若没有锁机制&#xff0c;可能出现“甲修改了背景色&#xff0c;乙…

C++ 设计模式《复制粘贴的奇迹:小明的原型工厂》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; &#x1f4d6; 背景故事 创业初期&#xff0c;小明每天加班写配送路线、配送策略、营销套餐。可当业务做大后&#xff0c;他发现大家常常下单“上次那个套餐”—— “老…

【Elasticsearch】映射:fielddata 详解

映射&#xff1a;fielddata 详解 1.fielddata 是什么2.fielddata 的工作原理3.主要用法3.1 启用 fielddata&#xff08;通常在 text 字段上&#xff09;3.2 监控 fielddata 使用情况3.3 清除 fielddata 缓存 4.使用场景示例示例 1&#xff1a;对 text 字段进行聚合示例 2&#…

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…

PlayDiffusion上线:AI语音编辑进入“无痕时代”

在语音合成与语音编辑领域&#xff0c;一个长期存在的挑战是如何在修改语音内容的同时&#xff0c;保持原始语音的自然性、连贯性和说话人特征。近日&#xff0c;一款名为 PlayDiffusion 的新型 AI 语音修复模型应运而生&#xff0c;成功实现了这一目标。 PlayDiffusion 是一个…

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…

微信小程序前端面经

一、技术栈与编码能力&#xff08;10min&#xff09; 1. Vue 3 & Composition API Q1&#xff1a;请解释一下 ref 和 reactive 的区别&#xff1f;你在项目中是如何使用的&#xff1f; 答&#xff1a;ref是包装一个原始值或对象&#xff0c;通过.value访问&#xff0c;r…

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…

Houdini POP入门学习07 - 分组

使用PopGroup可对粒子进行分组操作&#xff0c;并通过表达式从而更灵活的处理粒子行为。 1.创建box作为发射器&#xff0c;连接popnet节点。 2.双击进入popnet&#xff0c;添加popwind添加向上风力。现在播放粒子可见粒子向上方移动。 3.添加popgroup进行分组&#xff0c;开启…

机器学习复习3--模型评估

误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为&#xff1a;误差&#xff08;error&#xff09;。 误差定义&#xff1a; ①在训练集上的误差称为训练误差&#xff08;training error&#xff09;或经验误差&#xff08;empirical error&#x…

Docker 镜像上传到 AWS ECR:从构建到推送的全流程

一、在 EC2 实例中安装 Docker&#xff08;适用于 Amazon Linux 2&#xff09; 步骤 1&#xff1a;连接到 EC2 实例 ssh -i your-key.pem ec2-useryour-ec2-public-ip步骤 2&#xff1a;安装 Docker sudo yum update -y sudo amazon-linux-extras enable docker sudo yum in…

MobileNet 改进:基于MobileNetV2和SSPP的图像分类

1.创新点分析 在计算机视觉领域,高效的图像分类模型一直是研究热点。 本文将详细解析一个结合了MobileNetV2和空间金字塔池化(SSPP)的深度学习模型实现。 模型概述 这个代码实现了一个轻量级但功能强大的图像分类器,主要包含两个核心组件: MobileNetV2作为特征提取器 自定…

Java中List的forEach用法详解

在 Java 中&#xff0c;List.forEach() 是 Java 8 引入的一种简洁的遍历集合元素的方法。它基于函数式编程思想&#xff0c;接受一个 Consumer 函数式接口作为参数&#xff0c;用于对集合中的每个元素执行操作。 基本语法 java 复制 下载 list.forEach(consumer); 使用示…

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…

计算机视觉与深度学习 | 基于MATLAB的相机标定

基于MATLAB的相机标定:原理、步骤与代码实现 相机标定 基于MATLAB的相机标定:原理、步骤与代码实现MATLAB相机标定完整流程1. 准备工作2. 采集标定图像3. 导入图像并检测角点4. 生成世界坐标5. 执行相机标定6. 分析标定结果7. 应用标定结果校正图像相机标定关键概念相机参数类…

物联网专业核心课程以及就业方向

物联网专业作为信息技术与产业应用深度融合的交叉学科&#xff0c;其课程体系覆盖硬件、软件、网络、数据等全链条技术&#xff0c;就业方向则随智能技术普及呈现多元化趋势。以下是基于最新行业动态与教育实践的系统分析&#xff1a; &#x1f4da; 一、物联网专业核心课程体系…

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…