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

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 本身不是“处理多种类型数据”的结构,但结合不同通道的类型可以做到这一点。

相关文章:

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...