
本文旨在深入探讨Go语言的并发模型,重点解析Goroutines、Channels的工作原理及其与Go调度器之间的关系。通过分析一个具体的并发示例,我们将揭示Go程序执行顺序的非确定性,并提供如何使用Channels进行有效同步和通信的策略,以确保程序行为符合预期。
Go语言以其内置的并发原语而闻名,这些原语使得编写并发程序变得简单而高效。核心概念包括Goroutines(轻量级线程)和Channels(用于Goroutines之间通信的管道)。然而,初学者在理解Go调度器如何管理这些并发任务时,常会遇到一些困惑,尤其是在涉及执行顺序和同步时。
在Go中,Goroutine是由Go运行时管理的轻量级执行单元。它们比传统操作系统线程开销更小,可以轻松启动数千甚至数百万个。Channels是Goroutines之间进行通信和同步的主要方式。它们允许Goroutines安全地发送和接收数据,从而避免了传统并发编程中常见的竞态条件。
一个典型的Go并发程序会创建多个Goroutines,并通过Channels协调它们的执行。例如,以下代码片段展示了两个Goroutines (display 和 sum),它们各自执行一些任务,并通过一个布尔型Channel (c) 发送完成信号。主Goroutine (main) 则等待从这个Channel接收信号。
package main
import (
"fmt"
"time" // 引入time包用于模拟耗时操作
)
// display Goroutine打印一条消息并发送完成信号
func display(msg string, c chan bool) {
fmt.Println("display first message:", msg)
c <- true // 发送完成信号
}
// sum Goroutine执行一个长时间的计算并发送完成信号
func sum(c chan bool) {
sumVal := 0
// 模拟一个非常耗时的计算
for i := 0; i < 10000000000; i++ {
sumVal++
}
fmt.Println(sumVal)
c <- true // 发送完成信号
}
func main() {
c := make(chan bool) // 创建一个无缓冲的布尔型Channel
go display("hello", c) // 启动display Goroutine
go sum(c) // 启动sum Goroutine
<-c // 主Goroutine等待从Channel c接收一个信号
// 程序在接收到第一个信号后可能会退出
}在上述代码中,预期的输出可能会让初学者感到困惑。根据Go调度器的行为,程序的实际输出可能是:
display first message: hello 10000000000
而不是某些人可能预期的只打印一行 display first message: hello 就退出。
理解上述输出的关键在于Go调度器的工作方式。Go调度器是一个非确定性的组件,它负责在可用的操作系统线程上调度Goroutines的执行。它会根据内部算法在不同的Goroutines之间进行切换(抢占式调度),以实现并发执行的效果。这意味着Goroutines的精确执行顺序是无法保证的,并且可能在每次运行程序时有所不同。
对于上述示例,一个可能的执行序列如下:
这个序列解释了为什么两个Goroutine的打印输出都可能在 main Goroutine退出之前出现。关键在于 main 函数中的 <-c 只等待并接收 一个 值。由于 display 和 sum 都尝试向 c 发送值,main 只需要等待其中 任意一个 Goroutine成功发送即可。在上述场景中,尽管 sum 花了很长时间,但它和 display 的打印操作都可能在 main 接收到第一个信号之前完成。
如果需要确保特定的执行顺序或等待所有Goroutines完成,我们需要更精细的同步机制。
如果 main Goroutine需要等待所有启动的Goroutines都完成它们的任务并发送信号,那么它需要从Channel中接收相应数量的信号。
package main
import (
"fmt"
"time"
)
func display(msg string, c chan bool) {
fmt.Println("display first message:", msg)
time.Sleep(100 * time.Millisecond) // 模拟一些工作
c <- true
}
func sum(c chan bool) {
sumVal := 0
for i := 0; i < 1000000000; i++ { // 缩短循环以方便演示
sumVal++
}
fmt.Println(sumVal)
c <- true
}
func main() {
c := make(chan bool)
go display("hello", c)
go sum(c)
// 等待两个Goroutine都发送完成信号
<-c // 等待display或sum中的一个
<-c // 等待另一个
fmt.Println("所有Goroutine已完成并发送信号。")
}通过两次 <-c 操作,main Goroutine会阻塞直到从Channel c 接收到两个值,从而确保 display 和 sum 都已执行到发送信号的步骤。
更推荐的做法是使用 sync.WaitGroup 来等待一组Goroutines完成:
package main
import (
"fmt"
"sync"
"time"
)
func displayWithWG(msg string, wg *sync.WaitGroup) {
defer wg.Done() // Goroutine完成后调用Done
fmt.Println("display first message:", msg)
time.Sleep(100 * time.Millisecond)
}
func sumWithWG(wg *sync.WaitGroup) {
defer wg.Done() // Goroutine完成后调用Done
sumVal := 0
for i := 0; i < 1000000000; i++ {
sumVal++
}
fmt.Println(sumVal)
}
func main() {
var wg sync.WaitGroup
wg.Add(2) // 设置需要等待的Goroutine数量
go displayWithWG("hello", &wg)
go sumWithWG(&wg)
wg.Wait() // 阻塞直到所有Goroutine都调用了Done
fmt.Println("所有Goroutine已完成。")
}如果目标是只获取第一个完成的Goroutine的结果,并立即退出程序,那么Channel应该被设计为携带实际的结果,并且主Goroutine只接收一个结果。
package main
import (
"fmt"
"time"
)
// displayResult Goroutine发送其结果到Channel
func displayResult(msg string, resultChan chan string) {
time.Sleep(50 * time.Millisecond) // 模拟较快完成
resultChan <- "Display Goroutine: " + msg
}
// sumResult Goroutine发送其结果到Channel
func sumResult(resultChan chan string) {
sumVal := 0
for i := 0; i < 1000000000; i++ { // 模拟较慢完成
sumVal++
}
resultChan <- fmt.Sprintf("Sum Goroutine: %d", sumVal)
}
func main() {
resultChan := make(chan string) // 创建一个用于传递结果的Channel
go displayResult("hello", resultChan)
go sumResult(resultChan)
// 主Goroutine等待并打印第一个收到的结果
fmt.Println("第一个完成的任务结果:", <-resultChan)
// 程序在接收到第一个结果后立即退出。
// 另一个Goroutine可能仍在运行,但其结果不会被处理。
}在这个例子中,main Goroutine只从 resultChan 接收一个字符串值。无论 displayResult 或 sumResult 哪个先将结果发送到 resultChan,main 都会接收到它,打印,然后程序退出。这有效地实现了“谁先完成,就用谁的结果”的模式。
理解Go调度器如何管理Goroutines以及如何有效地使用Channels是编写健壮、高效Go并发程序的基石。通过实践和细致的思考,可以避免常见的并发陷阱,并充分利用Go语言的并发优势。
以上就是深入理解Go并发:Goroutines、Channels与调度器行为的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号