协程执行顺序引发的问题

引言

在Golang中,因为协程执行的顺序是不固定的,如果不在代码里进行控制,可能就会导致预期外的输出。
本文通过分析一段代码的执行来介绍这种情况,以及可行的控制协程执行顺序的方法:

  1. sleep()
  2. 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)
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高冷小伙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值