func main() {
for i := 0; i < 10; i++ {
// 开一个Goroutine(相当于开一个协程)并发地执行函数,同时主程序也会继续执行
// (主程序也是一个Goroutine)
go func(i int) {
for {
// 进行I/O操作时会交出控制权
fmt.Printf("Hello from "+"goroutine %d\n", i)
}
}(i)
}
// 一旦main函数退出,所有的Goroutine就会被杀死
time.Sleep(time.Millisecond)
}
Goroutine是一种协程(Coroutine)。
协程是“轻量级”的线程。之所以说是“轻量级”是因为
协程属于非抢占式多任务处理,即由协程主动决定是否交出控制权;
而线程属于抢占式多任务处理,即由操作系统来决定控制权给谁。
线程是操作系统层面的多任务,而协程是编译器/解释器/虚拟机层面的多任务。
Goroutine是编译器级别的多任务,由Go语言调度器来调度。
多个Goroutine可能在一个或多个线程上运行,由Go语言调度器决定。
定义Goroutine:
任何函数只需加上go关键字就能成为协程并送给调度器运行
func main() {
var a [10]int
for i := 0; i < 10; i++ {
// 开一个Goroutine(相当于开一个协程)并发地执行函数,同时主程序也会继续执行
// (主程序也是一个Goroutine)
go func(i int) {
for {
a[i]++
runtime.Gosched() // 手动交出控制权
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
// [1709 809 1563 1564 1627 1628 1549 1584 833 1231]
}
子程序(函数调用)是协程的一个特例:
左图为子程序(函数调用),main函数调用doWork函数,把控制权交给doWork函数。doWork函数执行完毕后返回main函数,控制权又回到main函数手里。main函数和doWork函数之间数据和控制权只能单向传递。
右图为协程,main函数和其过程中所开的协程doWork之间有一条双向的通道,实现数据、控制权双向流通。
main和doWork这两个协程可能运行在一个线程里,也可能不在一个线程里。
Go语言的协程(Goroutine):
上图为一个Go语言进程,其中包含一个调度器,调度器负责调度协程。
一个线程中可能有一个协程,也可能有多个协程。
Goroutine和传统意义上的协程有一点区别:
协程属于非抢占式多任务处理,除非协程主动交出控制权,否则不能抢占其控制权;
Goroutine大体上也是非抢占式的,但是调度器会在合适的点对控制权进行切换,切换的点我们并不能完全控制。
Goroutine可能的控制权切换点(仅作参考,不能保证在这些点一定切换,也不能保证在其他地方不切换):
①I/O,select
②channel
③等待锁
④函数调用(有时)
⑤runtime.Gosched()