(Go语言)使用两个协程按顺序交替打印 1 - n ,一个协程打印偶数,一个协程打印奇数

Go语言使用两个协程按顺序交替打印 1 - n ,一个协程打印偶数,一个协程打印奇数

方法一:使用两个缓冲为1的channel + sync.WaitGroup

为什么需要使用有缓冲的channel?

因为如果不使用带缓冲的channel会导致死锁问题,死锁的原因是当其中一个协程打印结束后,即执行完以后,另外一个协程就不能在向channel写数据了,导致死锁。
例如:
n = 3时,

  1. 打印奇数的协程先打印1
  2. 打印偶数的协程打印2,然后该协程执行完了
  3. 打印奇数的协程打印3,然后再向channel写数据时
    报错:fatal error: all goroutines are asleep - deadlock!
    对于无缓冲的通道,能够向通道写入数据的前提是必须有另外一个协程在读取通道
func printNumber(n int) {
	c1 := make(chan bool, 1)
	c2 := make(chan bool, 1)
	wg := sync.WaitGroup{}
	wg.Add(2)

	go func(n int) {
		defer wg.Done()
		for i := 2; i <= n; i += 2 {
			<-c1
			fmt.Println(i)
			c2 <- true
		}
	}(n)

	go func(n int) {
		defer wg.Done()
		for i := 1; i <= n; i += 2 {
			<-c2
			fmt.Println(i)
			c1 <- true
		}
	}(n)
	
	// 启动第一个goroutine,因为从1开始,所以先启动打印奇数的协程
	c2 <- true 
	
	wg.Wait()
}

func main() {
	printNumber(10)
}

那我就想用没有缓冲的通道实现可以吗?

既然我们知道它是因为什么才导致死锁的,那我们就可以去尝试着避免这种情况,不就是因为我们向通道写数据时,没有另外的一个协程接收数据嘛,那我们就判断一下,如果没有协程接收数据,我们就不向通道里面写数据了。

func printNumber(n int) {
	c1 := make(chan bool)
	c2 := make(chan bool)
	wg := sync.WaitGroup{}
	wg.Add(2)

	go func(n int) {
		defer wg.Done()
		for i := 2; i <= n; i += 2 {
			<-c1
			fmt.Println(i)
			// 只有当小于n时才向通道写数据,等于n时说明是
			// 最后一个需要打印的数了,不需要再向通道里写数据了。
			if i < n {
				c2 <- true
			}
		}
	}(n)

	go func(n int) {
		defer wg.Done()
		for i := 1; i <= n; i += 2 {
			<-c2
			fmt.Println(i)
			if i < n {
				c1 <- true
			}
		}
	}(n)

	// 启动第一个goroutine,因为从1开始,所以先启动打印奇数的协程
	c2 <- true

	wg.Wait()
}

func main() {
	printNumber(10)
}
方法二:无缓冲channel + sync.WaitGroup

无缓冲的channel,只有当读写同时就绪时才不会阻塞;两个协程的逻辑是一样的,会同时进入各自的if语句,即此时i值是相同的,但只有一个if满足条件,所以同一时间只有一个协程可以打印,另外一个被阻塞。

func printNumber(n int) {
	c1 := make(chan bool)
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func(n int) {
		defer wg.Done()
		for i := 0; i <= n; i++ {
			c1 <- true
			if i%2 == 0 {
				fmt.Println(i)
			}
		}
	}(n)

	go func(n int) {
		defer wg.Done()
		for i := 0; i <= n; i++ {
			<-c1
			if i%2 != 0 {
				fmt.Println(i)
			}
		}
	}(n)

	wg.Wait()
}

func main() {
	printNumber(10)
}
思考

方法二中还要判断i是奇数还是偶数,可不可以不用判断,想方法一中那样,用i+=2的形式?

// 该方法是错误的
func printNumber(n int) {
	c1 := make(chan bool)
	wg := sync.WaitGroup{}
	wg.Add(2)

	go func(n int) {
		defer wg.Done()
		for i := 2; i <= n; i += 2 {
			c1 <- true
			fmt.Println(i)
		}
	}(n)

	go func(n int) {
		defer wg.Done()
		for i := 1; i <= n; i += 2 {
			<-c1
			fmt.Println(i)
		}
	}(n)

	c1 <- true

	wg.Wait()
}

func main() {
	printNumber(10)
}

答案:不可以 ,会一下错误:

fatal error: all goroutines are asleep - deadlock!

分析:
对于方法二中的实现:
两个goroutine通过交替发送和接收布尔值来实现同步,以确保按顺序打印数字。
第一个goroutine中的循环从1到n,它在每个迭代中先发送布尔值true到通道c1,然后检查当前数字是否为偶数,如果是偶数则打印该数字。
第二个goroutine中的循环也从1到n,它在每个迭代中先从通道c1接收布尔值,然后检查当前数字是否为奇数,如果是奇数则打印该数字。
通过这种交替的发送和接收布尔值的方式,我们可以确保每个goroutine在打印数字之前都会等待另一个goroutine的信号。当第一个goroutine发送布尔值到通道时,第二个goroutine会被阻塞,直到接收到布尔值。同样,当第二个goroutine接收布尔值时,第一个goroutine会被阻塞,直到发送下一个布尔值。
这种同步机制确保了两个goroutine的执行顺序,因为每个goroutine都需要等待另一个goroutine的信号才能继续执行。这样,打印的顺序就会按照从0到n的顺序进行,且偶数和奇数交替出现。
对于思考中的错误实现:
逻辑有所不同。第一个goroutine直接打印偶数,不需要等待接收布尔值,因此它可以顺序地打印所有偶数。第二个goroutine直接打印奇数,并在每个数字之前等待接收布尔值。由于两个goroutine的循环逻辑不同,它们不需要交替发送和接收布尔值来实现同步。

总结起来,方法二的函数中的交替发送和接收布尔值的机制是为了确保两个goroutine的顺序执行,使打印的数字按顺序出现。而思考的函数中,由于两个goroutine的逻辑不同,它们可以独立执行而不需要交替的同步。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值