今天这篇笔记重点讲goroutine
首先怎么定义goroutine
很简单,在方法前面加上go就可以了
func main() {
go sayHello()
}
func sayHello() {
fmt.Println("hello")
}
也可以直接这样写, 基于匿名函数
go func() {
fmt.Println("hello")
}()
go 语言至少有一个main goroutine, 另外调用的sayhello goroutine和main goroutine并发执行,会在main goroutine退出后退出,所以我们上面的代码是有问题的,它不会打印出"hello". 因为main goroutine退出了,它没有机会执行。我们需要用到WaitGroup在main goroutine上面等待它,如下代码
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello")
}()
wg.Wait()
wg.Add(1), 表示会执行一次, wg.Done就是说执行了一次。 wg.Wait()会等待add的一次done.
main goroutine 可以和 goroutine共享相同的地址空间执行,如下代码
var wg sync.WaitGroup
salutation := "hello"
wg.Add(1)
go func() {
defer wg.Done()
salutation = "welcome"
}()
wg.Wait()
fmt.Println(salutation)
会打印出“welcome”, salutation变量在goroutine中被改变后,也会在main goroutine中生效。
我们来看下面的代码会输出什么
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
wg.Add(1)
go func() {
defer wg.Done()
mt.Println(salutation)
}()
}
wg.Wait()
它会打印三个good day, 为什么呢? 作者的解释是在goroutine开始之前循环有很高的概率会退出,salutation的值不在范围之内, go 语言运行时会足够小心的将变量salutation值得引用仍然保留,由内存转移到堆。 我不是特别明白作者得解释,我自己理解下,感觉是for循环是main goroutine, 它执行for很快,调用goroutine得时候它已经循环完了,所以就拿到最后得值了。
这个我们想打印三个不同得值,使用下面得代码
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
wg.Add(1)
go func(value string) {
defer wg.Done()
fmt.Println(value)
}(salutation)
}
wg.Wait()
这样三个goroutine使用的是各自的副本。
goroutine的开销是什么样的呢?
一个goroutine占多少内存? 作者书上说大概是2.817KB, 我自己实验了下我的机器上是9.072KB,而OS线程的一般会是2M, 差距还是有些大,所以我们说启动百万的goroutine也是很正常的,而线程一般几十个就不错了。 下面是示例代码
func main() {
memConsumed := func() uint64 {
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
var c <-chan interface{}
var wg sync.WaitGroup
noop := func() {
wg.Done()
<-c
}
const numGoroutines = 5e4
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i > 0; i-- {
go noop()
}
after := memConsumed()
fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)
}
numGoroutines是我们想创建的goroutine数量
noop 这个方法一直等channel里面的value,它不会退出,创建了的goroutine一直在内存中。
memConsumed 方法统计当前的内存, 我们在开始的时候统计下,在结束的时候统计下,相减后就得到消耗的内存。
作者还列举了goroutine的上下文切换耗时225ns, 而线程切换1467ns, 相差也比较大,
最后
作者得出的结论是使用goroutine非常廉价
文章评论