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 ~]
Thread 1 (LWP 10773):
可以看到的确只有一个线程在运行。
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。