引言
在Golang中,因为协程执行的顺序是不固定的,如果不在代码里进行控制,可能就会导致预期外的输出。
本文通过分析一段代码的执行来介绍这种情况,以及可行的控制协程执行顺序的方法:
- sleep()
- waitGroup
实例分析
代码
func NewRingBuffer(inCh, outCh chan int) *ringBuffer {
return &ringBuffer{
inCh: inCh,
outCh: outCh,
}
}
type ringBuffer struct {
inCh chan int
outCh chan int
}
func (r *ringBuffer) Run() {
for v := range r.inCh {
select {
case r.outCh <- v:
default:
<-r.outCh // pop one item from outchan
r.outCh <- v
}
}
close(r.outCh)
}
func main() {
inCh := make(chan int)
outCh := make(chan int, 4)
rb := pkg.NewRingBuffer(inCh, outCh)
go rb.Run()
for i := 0; i < 10; i++ {
inCh <- i
}
close(inCh)
//time.Sleep(time.Millisecond * 50)
for res := range outCh {
fmt.Println(res)
}
}
上面代码的作用是,声明ringBuffer环状缓冲区;主协程往inCh写10个数的同时,有一个协程异步的读取数据,并写到outCh中;
outCh是一个4缓冲区大小的channel,如果outCh没满就直接写入,否则把outCh的首部元素移除再添加;
因此,预期输出应该是:6、7、8、9。
但实际的输出是:5、6、7、8、9。
分析
多出来的“5”,是因为协程的执行顺序不可控,当主协程执行到
for i := 0; i < 10; i++ {
inCh <- i
}
close(inCh)
时,此时outCh中是5、6、7、8;run协程还没有继续执行,就开始遍历outCh:
for res := range outCh {
fmt.Println(res)
}
然后输出阻塞后,run协程才继续执行,把9写到outCh中,因此最后的输出结果是5、6、7、8、9.
解决办法
sleep()
使用sleep()函数,能让当前协程让出CPU,暂停执行一段时间。
对于这种方法,可以对上面函数进行如下改造:
close(inCh)
time.Sleep(time.Millisecond * 50)
// 新增
for res := range outCh {
fmt.Println(res)
}
WaitGroup
使用waitGroup可以实现协程间通信,对于这段例子,通过wg保证run()函数执行完后,在对outCh进行输出。
改造方法如下:
func (r *ringBuffer) RunWithWg(wg *sync.WaitGroup) {
defer wg.Done()
for v := range r.inCh {
select {
case r.outCh <- v:
default:
<-r.outCh // pop one item from outchan
r.outCh <- v
}
}
close(r.outCh)
}
func TestRaceWithWg(t *testing.T) {
inCh := make(chan int)
outCh := make(chan int, 4)
rb := pkg.NewRingBuffer(inCh, outCh)
var wg sync.WaitGroup
wg.Add(1)
go rb.RunWithWg(&wg)
go func() {
for i := 0; i < 10; i++ {
inCh <- i
}
close(inCh)
}()
wg.Wait()
for res := range outCh {
fmt.Println(res)
}
}
661

被折叠的 条评论
为什么被折叠?



