Goroutine
是Go
语言并发编程中的一个重要概念,事实上我们可以把它理解成为非常轻量级的线程(thread
)或协程(coroutine
)。每个运行的 Go
程序都至少包含一个goroutine
——运行main
函数的goroutine
。
创建goroutine
要使用go
语句:
go function(arguments)
go func(parameters) { block } (arguments)
go
后面要跟一个已经定义的函数或是一个现定义的匿名函数。执行完这条语句后,就会启动一个独立的,和其它程序中已有的goroutine
并行运行的新的goroutine
。
runtime
程序包中提供了一个GOMAXPROCS
函数,它可以用来设定实际上有多少个操作系统线程来执行Go
程序。截止到目前为止(1.4.2
版本),这个值一直默认为1
。从1.5
版本起,Go
的开发者打算把这个默认值改为可以使用的CPU
个数,并认为这会改善Go
程序的性能。看一个简单的程序:
package main
import (
"fmt"
"time"
"runtime"
)
func sleep(s string) {
for {
fmt.Println(s)
time.Sleep(time.Second)
}
}
func main() {
fmt.Println(runtime.GOMAXPROCS(0))
go sleep("Hello")
go sleep("World")
time.Sleep(1000 * time.Second)
}
main
函数第一行打印了当前系统默认设置的GOMAXPROCS
的值。另外在main goroutine
里启动了两个goroutine
,分别交替地打印“Hello
”和“World
”。执行如下:
1
Hello
World
Hello
World
Hello
World
Hello
World
......
可以看到默认的GOMAXPROCS
的值的确为1
。“Hello
”和“World
”也被交替打印了。在执行过程中,使用pstack
命令查看进程的线程调用栈:
[root@Fedora ~]# pstack 10773
Thread 1 (LWP 10773):
#0 runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:278
#1 0x000000000042b197 in runtime.futexsleep () at /usr/local/go/src/runtime/os_linux.c:49
#2 0x000000000040adae in runtime.notesleep (n=0x54df18 <runtime.m0+216>) at /usr/local/go/src/runtime/lock_futex.go:145
#3 0x000000000042e669 in stopm () at /usr/local/go/src/runtime/proc.c:1178
#4 0x000000000042f4c2 in findrunnable () at /usr/local/go/src/runtime/proc.c:1487
#5 0x000000000042f881 in schedule () at /usr/local/go/src/runtime/proc.c:1575
#6 0x000000000042fb03 in runtime.park_m () at /usr/local/go/src/runtime/proc.c:1654
#7 0x000000000041f94a in runtime.mcall () at /usr/local/go/src/runtime/asm_amd64.s:186
#8 0x000000000054dab0 in runtime.g0 ()
#9 0x000000000042de04 in runtime.mstart () at /usr/local/go/src/runtime/proc.c:836
#10 0x000000000041f84c in runtime.rt0_go () at /usr/local/go/src/runtime/asm_amd64.s:106
#11 0x0000000000000001 in ?? ()
#12 0x00007ffd43803348 in ?? ()
#13 0x0000000000000001 in ?? ()
#14 0x00007ffd43803348 in ?? ()
#15 0x0000000000000000 in ?? ()
可以看到的确只有一个线程在运行。
Goroutine
的调度器(scheduler
)并不是完全抢占的(fully-preemptive
),当有GOMAXPROCS
个goroutine
并行运行时,运行的goroutine
必须要放弃执行权,其它goroutine
才有机会得到运行(比方说,调用time.Sleep()
)。此外,取决于调度器的实现,goroutine
可能在执行任何函数时,都会放弃CPU
。看下面这个程序:
package main
import (
"fmt"
)
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
执行结果如下:
hello
hello
hello
hello
hello
main goroutine
运行起来以后,并没有给另外一个goroutine
打印“world
”的机会。随着main goroutine
执行完毕,这个程序也退出了。修改程序如下:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
time.Sleep(time.Second)
}
这次执行结果如下:
hello
hello
hello
hello
hello
world
world
world
world
world
在main
函数最后加了一行time.Sleep(time.Second)
,这样main goroutine
就可以把CPU
的执行权让出来,所以另外一个goroutine
得以运行。
参考资料:
(1)Go 1.5 GOMAXPROCS Default;
(2)Some questions about goroutine;
(3)Why is time.sleep required to run certain goroutines。