问题
先看报错信息
Table: 0 grass: 0 match: 0
Apple smokes a cigarette
Table chooses paper: Sandy
Table: 0 grass: 1 match: 1
Table: 0 grass: 1 match: 0
Table: 0 grass: 0 match: 0
Sandy smokes a cigarette
panic: sync: negative WaitGroup counter
goroutine 6 [running]:
sync.(*WaitGroup).Add(0xc0000140c0, 0xffffffffffffffff)
D:/Program Files/Go1136/src/sync/waitgroup.go:74 +0x140
sync.(*WaitGroup).Done(...)
D:/Program Files/Go1136/src/sync/waitgroup.go:99
main.smoker(0xc0000044c0, 0x4d8bb5, 0x5, 0x0, 0xc0000341c0)
d:/code-base/gomod/gott/main.go:84 +0x668
created by main.main
d:/code-base/gomod/gott/main.go:102 +0x1de
exit status 2
D:\code-base\gomod\gott>
分析
刚开始的时候,下意识的认为是 WaitGroup
使用不当。还查阅了 地址stackoverflow
但实际上的代码表现出来是前半段正常,然后突然不正常,感觉就像多减了一次
无限循环抽烟问题
下面是循环模拟抽烟问题
package main
import (
"math/rand"
"fmt"
"sync"
"time"
)
const (
paper = iota
grass
match
)
var smokeMap = map[int]string{
paper:"paper",
grass:"grass",
match:"match",
}
var names = map[int]string{
paper:"Sandy",
grass:"Apple",
match:"Daisy",
}
type Table struct {
paper chan int
grass chan int
match chan int
}
func arbitrate(t *Table, smokers [3]chan int){
for {
time.Sleep(time.Microsecond*500)
next := rand.Intn(3)
fmt.Printf("Table chooses %s: %s\n", smokeMap[next],names[next])
switch next {
case paper:
t.grass <- 1
t.match <- 1
case grass:
t.paper <- 1
t.match <- 1
case match:
t.grass <- 1
t.paper <- 1
}
for _, smoker := range smokers {
smoker <- next
}
wg.Add(1)
wg.Wait()
}
}
func smoker(t *Table, name string, smokes int, signal chan int){
var chosen = -1
for{
chosen = <-signal // 阻塞
if smokes != chosen {
continue
}
fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper),len(t.grass), len(t.match))
select{
case <- t.paper:
case <- t.grass:
case <- t.match:
}
fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
time.Sleep(10 * time.Millisecond)
select {
case <-t.paper:
case <-t.grass:
case <-t.match:
}
fmt.Printf("Table: %d grass: %d match: %d\n", len(t.paper), len(t.grass), len(t.match))
fmt.Printf("%s smokes a cigarette\n", name)
time.Sleep(time.Microsecond *500)
// 此处需要确保wg.Add在wg.Done之前执行
wg.Done()
time.Sleep(time.Millisecond *100)
}
}
const LIMIT = 1
var wg *sync.WaitGroup
func main() {
wg = new(sync.WaitGroup)
table := new(Table)
table.match = make(chan int, LIMIT)
table.paper = make(chan int, LIMIT)
table.grass = make(chan int, LIMIT)
var signals [3] chan int
for i := 0; i < 3; i++ {
signal := make(chan int , 1)
signals[i] = signal
go smoker(table,names[i], i, signal)
}
fmt.Printf("%s, %s, %s, sit with \n%s, %s, %s\n\n", names[0], names[1], names[2], smokeMap[0], smokeMap[1], smokeMap[2])
// 裁决谁在抽烟
arbitrate(table, signals)
}
正常情况下,应该表现如下无限循环
D:\code-base\gomod\gott>go run "d:\code-base\gomod\gott\main.go"
Sandy, Apple, Daisy, sit with
paper, grass, match
Table chooses match: Daisy
Table: 1 grass: 1 match: 0
Table: 1 grass: 0 match: 0
Table: 0 grass: 0 match: 0
Daisy smokes a cigarette
Table chooses paper: Sandy
Table: 0 grass: 1 match: 1
Table: 0 grass: 1 match: 0
Table: 0 grass: 0 match: 0
Sandy smokes a cigarette
Table chooses match: Daisy
Table: 1 grass: 1 match: 0
因果
后来排查发现,问题出在烟者goroutine
中的time.sleep
阻塞时间上 time.Microsecond
微秒
对于该goroutine 他的最大阻塞时间取决于 wg.Done()之后的毫秒级
time.Sleep(time.Millisecond *100)
函数arbitrate
执行时间 最多阻塞时长只有 微秒级
time.Sleep(time.Microsecond*500)
所以执行表现的效果就是,当函数与goroutine在首次循环同等微秒级时间段阻塞结束,goroutine还未结束,产生“错位”
解决
1.使用arbitrate 函数的阻塞时不小于goroutine最大阻塞时长
2.或干脆将微秒级时间全部改为毫秒级
3.或不改了,将goroutine中的wg.Done后面的阻塞代码删掉