Goroutine 与 线程
可增长的栈
每个OS线程有一个固定大小的栈内存(通常2MB),栈内存用于保存函数调用中的局部变量。
对于一个小的goroutine,比如仅仅等待一个WaitGroup
再关闭一个通道,2MB的栈太浪费了。对于复杂和深度递归函数,固定大小的栈不够大。
一个goroutine
生命周期开始时只有一个很小的栈(通常2KB),与OS线程不同的是,goroutine
的栈不是大小固定的,可以按需增大和缩小。
- 使用通道构造一个把任意多个
goroutine
串联再一起的流水程序。在内存耗尽之前你能创建的最大流水线级数是多少?一个值穿过整个流水线需要多久?
func main() {
var ch = make(chan int)
var number int = 1e6
fmt.Println("Creating goroutines",number)
startTime := time.Now()
var n = sync.WaitGroup{}
for i := 0; i <= number; i++ {
n.Add(1)
go func(in <-chan int, out chan<- int,n *sync.WaitGroup) {
out <- <-in
n.Done()
}(ch, ch,&n)
}
n.Add(-1)
fmt.Println("Creating finished",time.Since(startTime))
startTime = time.Now()
ch <- 1
n.Wait()
fmt.Println("Time: ", time.Since(startTime))
}
Output
Creating goroutines 1000000
Creating finished 6.6581723s
Time: 11.6568727s
当我尝试创建3*10^6
个goroutine时,我的goland
卡没了,就是自动关闭了。
下图是我重开的goland
,然后时间看不到了。
goroutine真是强大,可以随便开到10W个。
电脑配置
- i5-8265U
- 8g ddr4 2666
goroutine调度
OS线程由OS内核来调度。因为OS线程由内核来调度,所有控制权权限从一个线程到另一个线程需要一个完整的上下文切换
(context switch)。
Go运行时包含一个自己的调度器,这个调度器使用一个称为m:n
调度的技术,它可以复用/调度m个goroutine到n个OS线程。
与操作系统线程调度器不同,Go调度器由特定的Go语言结构来触发。因为它不需要切换到内核语境,所以调用一个goroutine比调用一个线程成本低很多。
- 写一个程序,两个goroutine通过两个无缓冲通道来互相转发消息。这个程序能每秒多少次通信?
func main() {
var counter int64
var t = time.NewTimer(1 * time.Second)
var chIn = make(chan struct{})
var chOut = make(chan struct{})
var done = make(chan struct{})
go func() {
for {
select {
case <-done:
return
default:
chIn <- struct{}{}
<-chOut
counter++
}
}
}()
go func() {
for {
select {
case <-done:
return
default:
<-chIn
chOut <- struct{}{}
}
}
}()
select {
case <-t.C:
close(done)
fmt.Println("Counter:",counter)
}
}
Output
Counter: 1816315
GOMAXPROCS
Go调度器使用GOMAXPROCS
参数来确定需要使用多少个OS线程来同时执行Go代码。
默认是机器的CPU内核数(我们有超线程啊,一个掰成俩来用!?)。
正在休眠或者正在被通道通信阻塞的goroutine
不需要占用线程。阻塞在I/O和其它系统调中或调用非Go语言写的函数的goroutine
需要一个独立的OS线程,但这个线程不计算在GOMAXPROCS
内
- 那么问题来了,前面的两个小练习都是基于默认的
GOMAXPROCS
运行的,如果修改GOMAXPROCS
会发生什么?
//GOMAXPROCS = 1
Creating goroutines 1000000
Creating finished 5.5462063s
Time: 9.256258s
//GOMAXPROCS = 4
Creating goroutines 1000000
Creating finished 5.7835657s
Time: 3.1585553s
//GOMAXPROCS = 1
Counter: 3264829
//GOMAXPROCS = 4
Counter: 1736502
嘤嘤?
个人理解
- 对于练习1
我沮丧的发现,仅仅增加线程的数量,不能保证性能一定会得到提升。可能是因为开了太多的goroutine
,因此收到其它环境的影响就会比较大,但是对于许多个goroutine
的情况下,开多个线程往往会取得比较好的结果。具体的瓶颈在哪里我没法去深度了解,也不知道。 - 对于练习2
受制于OS调度开销的影响。这里开了两个goroutine
但是两个不能同时运行,其中有一个处于阻塞状态,这样的话,如果开多个个线程,去处理一个goroutine
确实有种帮倒忙的感觉。
这两个例子就是给我们理解goroutine
协程的轻量吧。
!!!此外goroutine
调度的因素很多,运行时也在不断变化,这里的结果真的有时候差距很大。!!!
goroutine 没有标识
这个看的不是很懂。。。。呜呜...
结尾
goroutine
与线程
的差距本质上是属于量变,但一个足够大的量会变成质变。
嘤嘤嘤
goroutine
好用就对了!