goroutine
go中使用goroutine来实现并发,与其它语言中的thread有所不同,goroutine更轻量,一个thread可以对应多个goroutine,所以在某些场景下,goroutine的并发效果要远好于thread,这也是go语言的一大优势。
go关键字
go中开启一个goroutine的方式就是在要实现并发的函数前面加一个go关键字,如
import (
"fmt"
"sync"
)
var (
x = 0
wg sync.WaitGroup
)
// x自增100次
func add() {
for i := 0; i < 100; i++ {
x++
fmt.Println(x)
wg.Done()
}
}
func main() {
wg.Add(200)
for i := 0; i < 2; i++ {
go add()
}
wg.Wait()
}
sync.WaitGroup
上面的例子中,如果没有wg.Add()、wg.Done()、wg.wait()这些,也许控制台上不会有任何输出。因为goroutine是异步的,main函数不会等add函数结束后才结束,main函数结束后,add函数自然就结束了,但main函数结束过快就会导致add函数中的输出还没打印出来,程序就结束了,控制台上就看不到输出结果,所以我们需要让main函数推迟结束,但使用time.Sleep()又过于死板,即使add函数提前结束了,main也依然会等待指定时间后才会结束。这就会造成资源的浪费。
- wg.Add():加几次任务
- wg.Done():减一次任务
- wg.Wait():当任务数为0时,就向下执行,否则就等待 以上是sync.WaitGroup的三个方法,这三个方法保证了goroutine执行结束后,main函数才能结束,而且是goroutine一结束,main函数就结束了,避免了无谓的等待,比较灵活。
GMP模型
G
G代表goroutine对象,它包括栈、指令指针以及对于调用goroutines很重要的其它信息,比如阻塞它的任何channel等。每次执行go都会创建一个G对象。
M
M(Machine),抽象化代表内核线程,记录内核线程栈信息,当goroutine调度到线程时,使用该goroutine自己的栈信息。M需要与P进行绑定。
P
P(Processor)是一个抽象的概念,并不是真正的物理CPU。所以当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务。所以P/M需要进行绑定,构成一个执行单元。
P决定了同时可以并发任务的数量,可通过GOMAXPROCS限制同时执行用户级任务的操作系统线程。可以通过runtime.GOMAXPROCS进行指定。在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1。
end
文章仅做个人学习交流,也许并不完全正确,欢迎指正;