Golang中如何理解goroutines的启动顺序
Golang中如何理解goroutines的启动顺序
package main
import "fmt"
func sum(a []int, c chan int) {
fmt.Println("ARRAY", a)
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[len(a)/2:], c) //first
go sum(a[0:len(a)/2], c) // second
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
在这个例子中,我们启动了两个goroutine,第一个(go sum(a[len(a)/2:], c))是包含值(-9, 4, 0)的int数组切片,第二个也是包含值(7, 2, 8)的int切片。我的问题是,虽然我们先启动了值为-5的goroutine,但为什么我们接收到的17比-5更快?另外,你能给我一些关于并发和通道的参考资料吗?
更多关于Golang中如何理解goroutines的启动顺序的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的
非常感谢
那么如果我多次运行代码,输出可能会从 17 -5 12 变成 -5 17 12 吗?
尝试限制 runtime.GOMAXPROCS(1) => 2 并尝试理解发生了什么。虽然没有明确的顺序定义 😛
让我重申一下:goroutine 的执行没有固定顺序,它们只是被放入栈中,运行时在某个时刻决定哪个 goroutine 会被启动。即使有 100 个 goroutine,可能第 77 个 goroutine 会先执行。当我们想要从通道接收数据时,如果 100 个 goroutine 都准备好发送数据,运行时会随机选择一个 goroutine,而其他 goroutine 必须等待下一次发送数据的机会,我的理解正确吗?
没有定义的执行顺序——它们是并发运行的。哪个协程恰好先开始和/或先完成是随机的。
也许这样理解会有所帮助:不要将"go"关键字视为"启动"一个协程,而是看作创建一个协程数据结构并将其放入可运行协程池中。运行时调度器会在某个时刻查看这个池子并开始执行这些协程,必要时可能会创建新线程来运行它们。
两个通道发送者哪个会先完成也是非确定性的。假设你的协程立即启动并瞬间完成。当代码执行到第一个<-c时(在... := <-c, <-c中),会有两个协程尝试向通道发送数据。哪个被选中执行第一次写入是随机的,其结果会存入x。另一个则会服务于第二次读取,结果存入y。
... := <-c, <-c
在Go语言中,goroutines的启动顺序并不保证执行完成顺序。当你使用go关键字启动goroutine时,这些goroutine会被调度到可用的操作系统线程上执行,但具体的执行时机由Go运行时调度器决定。
在你的代码示例中:
package main
import "fmt"
func sum(a []int, c chan int) {
fmt.Println("ARRAY", a)
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[len(a)/2:], c) // 计算 [-9, 4, 0] 总和为 -5
go sum(a[0:len(a)/2], c) // 计算 [7, 2, 8] 总和为 17
x, y := <-c, <-c // 从通道接收值
fmt.Println(x, y, x+y)
}
执行结果可能为:
ARRAY [-9 4 0]
ARRAY [7 2 8]
-5 17 12
或者:
ARRAY [7 2 8]
ARRAY [-9 4 0]
17 -5 12
关键点解释:
-
goroutine调度不确定性:虽然你按顺序启动goroutines,但Go运行时调度器决定了它们的实际执行顺序。
-
通道接收顺序:
x, y := <-c, <-c会按完成顺序接收值,而不是按启动顺序。 -
计算复杂度:计算
[7, 2, 8]和[-9, 4, 0]的复杂度相同,但调度器可能让其中一个先完成。
更明确的示例:
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Duration(id) * 100 * time.Millisecond) // 模拟不同耗时
c <- id
fmt.Printf("Worker %d completed\n", id)
}
func main() {
c := make(chan int)
// 按顺序启动,但完成顺序取决于执行时间
go worker(1, c) // 耗时较短
go worker(3, c) // 耗时较长
go worker(2, c) // 耗时中等
// 接收顺序将反映完成顺序
for i := 0; i < 3; i++ {
result := <-c
fmt.Printf("Received from worker %d\n", result)
}
}
并发和通道参考资料:
Go官方文档:
关键概念代码示例:
// 缓冲通道示例
func bufferedChannelExample() {
ch := make(chan int, 2) // 缓冲大小为2
ch <- 1
ch <- 2
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
}
// 使用select处理多个通道
func selectExample() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "from ch1" }()
go func() { ch2 <- "from ch2" }()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
goroutines的执行顺序由Go运行时调度,无法保证先启动的goroutine会先完成,这是Go并发模型的设计特性。

