goroutine学习笔记
最简单的goroutine例子
最简单的例子就是直接在main
函数中使用go
语句生成一个独立的goroutine
,如下:
package main
import "fmt"
func main() {
go fmt.Println("another goroutine")
fmt.Println("main goroutine")
}
但是我们执行下,大概率会发现只有输出中只有
main goroutine
而不是我们想象中的
another goroutine
main goroutine
这是因为main
函数没等到我们another goroutine
执行完毕就退出了。于是我们需要想个方式让main
等待another goroutine
执行完再退出,当然最简单的方法就是用time.Sleep
了:
package main
import (
"fmt"
"time"
)
func main() {
go fmt.Println("another goroutine")
fmt.Println("main goroutine")
time.Sleep(time.Second)
}
channel
在实战中不太可能会使用time.Sleep
让main
等待,因为我们不知道生成的独立goroutine
什么时候会结束。我们可以用channel
来进行main
和goroutine
的通讯,让goroutine
通知main
,任务结束了。
package main
import (
"fmt"
)
func main() {
done := make(chan struct{})
go func() {
defer close(done)
fmt.Println("another goroutine")
}()
fmt.Println("main goroutine")
<-done
}
channel
需要用make
函数生成,如果直接var done chan struct{}
而没有初始化,会panic: close of nil channel
;- 我们这个例子中
channel
的作用只是为了传递结果本身,而不是数据,因此可以使用不占内存的结构struct{}
; go
语句支持匿名函数,要记得go
语句调用的是个函数,所以第12行后面要带()
;<-done
会在main
中阻塞,直到在goroutine
中关闭channel
。
(可能的)输出如下:
main goroutine
another goroutine
多个goroutine
通常我们需要在main
中生成多个goroutine
,如果直接将上面的例子加上个for
循环,是否可以呢?
package main
import (
"fmt"
)
func main() {
done := make(chan struct{})
for i := 0; i < 5; i++ {
go func() {
defer close(done)
fmt.Println("another goroutine")
}()
}
fmt.Println("main goroutine")
<-done
}
(可能的)输出如下:
main goroutine
another goroutine
another goroutine
可以看到我们用的done
通道,在第二个goroutine
执行的时候就close
掉了,因此main
就退出了。
我们修改下程序,让done
作为传输数据的通道,每生成一个goroutine
,就往done
通道中发送1:
package main
import (
"fmt"
)
var (
count = 5
)
func main() {
done := make(chan int)
for i := 0; i < count; i++ {
go func() {
defer func() {
done <- 1
}()
fmt.Println("another goroutine")
}()
}
fmt.Println("main goroutine")
cnt := 0
for cnt != count {
cnt = cnt + <-done
}
}
sync.WaitGroup
go
官方提供了sync.WaitGroup
来做上面例子中channel
类似的事情,用sync.WaitGroup
非常清晰明了:
package main
import (
"fmt"
"sync"
)
var (
count = 5
)
func main() {
var wg sync.WaitGroup
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
wg.Add(1)
fmt.Println("another goroutine")
}()
}
fmt.Println("main goroutine")
wg.Wait()
}
wg.Add(1)
:往WaitGroup
中增加1;wg.Done()
:在WaitGroup
中减去1;wg.Wait()
:阻塞,直到WaitGroup
为0。
生产者消费者模型
在main
中设置生产者队列,在goroutine
中设置消费者队列,在上面的例子上修改如下:
package main
import (
"fmt"
"sync"
"time"
)
var (
queueSize = 10
goroutineCount = 5
)
func main() {
var wg sync.WaitGroup
queue := make(chan int)
for i := 0; i < goroutineCount; i++ {
go func() {
defer wg.Done()
wg.Add(1)
for item := range queue {
fmt.Printf("queue:%d sleep for %d seconds\n", item, item)
time.Sleep(time.Duration(item) * time.Second)
fmt.Printf("queue:%d finished\n", item)
}
}()
}
for j := 0; j < queueSize; j++ {
queue <- j
}
close(queue)
wg.Wait()
}
在上面的例子中:
for j := 0; j < queueSize; j++ {
queue <- j
}
close(queue)
是生产者,负责发送数据给队列(通道),而goroutine
则是消费者,使用range
语句从队列中循环获取消费数据,处理数据:
go func() {
defer wg.Done()
wg.Add(1)
for item := range queue {
fmt.Printf("queue:%d sleep for %d seconds\n", item, item)
time.Sleep(time.Duration(item) * time.Second)
fmt.Printf("queue:%d finished\n", item)
}
}()