Golang 并发编程(二): Goroutine
- 陈大剩
- 2025-01-22 00:49:25
- 76
Goroutine
说到 goroutine
(或称 G)就不得不提到 Go
语言特有的关键字 go
,它是用户程序启用 goroutine
的唯一途径,一条 go
语句意味着一个方法或函数的并发执行操作。例如:
go fmt.Println("hello world")
go
语句是由 go
关键字(请区分:关键字、函数)和表达式组成的,表达式就是描述针对若干操作数的计算方法的式子,表达式有很多种,其中包括调用表达式,调用表达式是针对函数或方法的调用(函数可以是命名的也可以是匿名的)。
// 匿名 goroutine
go func() {
fmt.Println("hello world")
}()
// 命名 goroutine
go funcName()
注意:
goroutine
中包含主goroutine
和 用户goroutine
。封装main
函数的goroutine
称为主goroutine
,主goroutine
负责管理整个生命运行周期,这里我们只介绍用户goroutine
,主goroutine
具体可以阅读源码。
使用
首先使用上列举几个并发常见的问题。
例1:主程序退出导致无法输出信息
如果使用 go 命令运行如下代码:
func main() {
go fmt.Println("hello world")
}
这将不会输出 “hello world” ,这是因为 主程序退出,在调用 go fmt.Println("hello world")
后,主程序没有任何阻塞或等待的代码,导致 main
函数在启动 goroutine
后立即返回并退出。由于主程序退出,所有的 goroutine
也被终止,即使它们还没有执行完。
func main() {
go fmt.Println("hello world")
time.Sleep(time.Second)
}
这里作用是用它的 goroutine
暂停一段时间,理想状态下,运行可以看到输出。但是,真实情况并不总是这样(因为调度器我们无法控制)。
例2:循环作用域变化无法输出正确信息
如果 golang
版本为 1.22
以下输出 name
时大概率只会出现 “wangwu”、”wangwu”、”wangwu”….
names := []string{"dasheng", "zhangsan", "lisi", "wangwu"}
for _, name := range names {
go func() {
fmt.Println(name)
}()
}
time.Sleep(time.Second)
这是因为循环作用域导致无法输出正确信息, golang 1.22
版本以上修复了此问题,但是正常解因使用传参进行,保持规范。
names := []string{"dasheng", "zhangsan", "lisi", "wangwu"}
for _, name := range names {
go func(s string) {
fmt.Println(s)
}(name)
}
time.Sleep(time.Second)
第二种代码不出错的原因是,name 变量的类型 string 是一个非引用类型,把一个值作为参数传给函数的时候,该值会被复制。但对于引用类型(切片、字典)的值来说,由于它类似于指向真正数据的指针,所以即便被复制了,外部修改也会反应到函数内部,所以使用引用类型无法使用传值。
题外话
我用这段代码去问面试候选人,没有几个答对的,你们说是为什么?
names := []string{"dasheng", "zhangsan", "lisi", "wangwu"}
for _, name := range names {
go func(s string) {
fmt.Println(s)
}(name)
}
time.Sleep(time.Second)