goroutine 和 chan,一个用于并发,另一个用于通信。没有缓冲的通道具有同步的功能,除此之外,sync 包也提
供了多个 goroutine 同步的机制,主要是通过 WaitGroup 实现的。
WaitGroup 用来等待多个 goroutine 完成,main goroutine 调用 Add 设置需要等待 goroutine 的数目,每一个
goroutine 结束时调用 Done(),Wait() 被 main 用来等待所有的 goroutine 完成。
主要数据结构和操作如下:
type WaitGroup struct {
// contains filtered or unexported fields
}
// 添加等待信号
func (wg*WaitGroup) Add (delta int)
// 释放等待信号
func (wg*WaitGroup) Done()
// 等待
func (wg*WaitGroup) Wait()
下面的程序演示如何使用 sync.WaitGroup 完成多个 goroutine 之间的协同工作。
package main
import (
"net/http"
"sync"
)
var wg sync.WaitGroup
var urls = []string{
"https://news.sina.com.cn/",
"https://www.bilibili.com/",
"https://www.qq.com/",
}
func main() {
for _, url := range urls {
//每一个url启动一个goroutine,同时给wg加1
wg.Add(1)
// 启动一个goroutine获取URL
go func(url string) {
// 当前goroutine结束后给wg计数减1 ,wg.Done()等价于wg.Add(-1)
// defer wg.Add(-1)
defer wg.Done()
// 发送http get请求并打印http返回码
resp, err := http.Get(url)
if err == nil {
println(resp.Status)
}
}(url)
}
// 等待所有HTTP获取完成
wg.Wait()
}
# 输出
501 Not Implemented
200 OK
200 OK
多线程中使用睡眠函数不优雅,直接用 sync.WaitGroup 保证一个 goroutine 刚退出就可以继续执行,不需要自
己猜需要 sleep 多久。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
// 启动一个goroutine就登记+1,启动十个就+10
wg.Add(10)
var count = 0
for i := 0; i < 10; i++ {
go func() {
// goroutine结束就登记-1
defer wg.Done()
for j := 0; j < 100000; j++ {
count++
}
}()
}
// 等待所有登记的goroutine都结束
wg.Wait()
// 346730
fmt.Print(count)
}
启动十个goroutine对count自增10w次,理想状况为100w,但由于没有锁,会出现实际情况远远小于并且不相等
的情况。为什么会出现这样的结果?因为自增并不是一个原子操作,很可能几个goroutine同时读到同一个数,自
增,又将同样的数写了回去。
共享资源是count变量,临界区是count++,临界区之前加锁,使其他goroutine在临界区阻塞,离开临界区解
锁,就可以解决这个 data race 的问题。go语言是通过 sync.Mutex 实现这一功能。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
// 定义锁
var mu sync.Mutex
wg.Add(10)
var count = 0
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for j := 0; j < 100000; j++ {
// 加锁
mu.Lock()
count++
// 解锁
mu.Unlock()
}
}()
}
wg.Wait()
// 1000000
fmt.Print(count)
}
把 Mutex 嵌入 struct,可以直接在这个 struct 上使用 Lock/Unlock。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
type Counter struct {
sync.Mutex
Count uint64
}
func main() {
var counter Counter
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for j := 0; j < 100000; j++ {
counter.Lock()
counter.Count++
counter.Unlock()
}
}()
}
wg.Wait()
// 1000000
fmt.Print(counter.Count)
}
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
type Counter struct {
mu sync.Mutex
count uint64
}
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
var counter Counter
// 启动一个goroutine就登记+1,启动十个就+10
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
// goroutine结束就登记-1
defer wg.Done()
for j := 0; j < 100000; j++ {
counter.Incr()
}
}()
}
// 等待所有登记的goroutine都结束
wg.Wait()
// 1000000
fmt.Print(counter.Count())
}