关于 go chann 阻塞的问题

20 天前
 timsims

这是 V2EX 从很久以前的一道面试题我直接拿来练手的,但是我并不理解死锁出现和不出现的原因

使用两个 goroutine 交替打印序列,一个 goroutinue 打印数字, 另外一个 goroutine 打印字母, 最终效果如下 12AB34CD56EF78GH910IJ 。

版本一,这个代码是能正常运行的,但结尾要通过 sleep 来阻塞


	ch1 := make(chan bool)
	ch2 := make(chan bool)

	go func() {
		// for loop to print 2 to 10, increase by 2
		for i := 2; i <= 10; i += 2 {
			<-ch1
			fmt.Print(i - 1)
			fmt.Print(i)
			ch2 <- true
		}
		close(ch2)
	}()

	go func() {
		str := "abcdefghij"
		// for loop to print a to j
		for i := 1; i < len(str); i += 2 {
			<-ch2
			fmt.Print(str[i-1 : i+1])
			ch1 <- true
		}
		close(ch1)
	}()

	ch1 <- true
	time.Sleep(10 * time.Second)

版本二,通过 done channel ,但这个版本就会出现死锁

	ch1 := make(chan bool)
	ch2 := make(chan bool)
	done := make(chan bool) // 新增 done 

	go func() {
		// for loop to print 2 to 10, increase by 2
		for i := 2; i <= 10; i += 2 {
			<-ch1
			fmt.Print(i - 1)
			fmt.Print(i)
			ch2 <- true
		}
		close(ch2)
	}()

	go func() {
		str := "abcdefghij"
		// for loop to print a to j
		for i := 1; i < len(str); i += 2 {
			<-ch2
			fmt.Print(str[i-1 : i+1])
			ch1 <- true
		}
		done <- true // 通知完成
		close(ch1) // ch1 关不关闭都会死锁
	}()

	ch1 <- true
	<-done // 等待完成通知

版本三,基于二调整,不死锁


	ch1 := make(chan bool, 1) // 和版本二的核心区别
	ch2 := make(chan bool)
	done := make(chan bool)

	go func() {
		// for loop to print 2 to 10, increase by 2
		for i := 2; i <= 10; i += 2 {
			<-ch1
			fmt.Print(i - 1)
			fmt.Print(i)
			ch2 <- true
		}
		close(ch2)
	}()

	go func() {
		str := "abcdefghij"
		// for loop to print a to j
		for i := 1; i < len(str); i += 2 {
			<-ch2
			fmt.Print(str[i-1 : i+1])
			ch1 <- true
		}
		done <- true
		close(ch1)
	}()

	ch1 <- true 
	<-done 

我自己的理解是, 版本二死锁的原因,ch1 是 unbuffered channel ,因为第二个协程循环最后,往 ch1 写入后没有取去,导致阻塞死锁

所以在版本三,把 ch1 改成 buffered channel 后,即使没有人取,也不会阻塞

但为什么同样是 unbuffered channel 的 ch1, 在版本一中却不会死锁呢?

另外 <-done 在主线程中扮演怎样的角色

619 次点击
所在节点    问与答
8 条回复
klesh
20 天前
总数不对。
版本一是各写了 5 次,没问题。不死锁
版本二 ch1 给写了 6 次,比 ch2 多了一次,最后一个写不进去了。死锁
版本三 ch1 buffer 了,可以写 6 次。不死锁
klesh
20 天前
done 的意义就是保证所有子线程完成吧?毕竟要提前定好 sleep 多久合适还是挺难的。
timsims
20 天前
@klesh 没看懂为什么为什么版本一写了 5 次? 这三个版本不都是在 main 写了 1 次, 协程 2 写了 5 次吗?
pandaex
20 天前
@timsims 第一个 sleep 约等于 waitwithtimeout 等待两个协程结束,超时了它就拉倒跑路,第二个 done 就死等,一定要等到子携程退出
timsims
20 天前
@pandaex 明白了,版本二则因为协程二的 ch1 阻塞了, done <- true 一直没执行,所有 main 中的 <-done 在死等
twig
20 天前
是不是有个东西叫 WaitGroup 跟这个有关系吗?(我也入门,不太清楚。)
pandaex
20 天前
@twig waitgroup 就是干这个的,和线程里面的 join 很像,还有个 errgroup 功能累死,但是 wait 可以拿到协程返回的错误
klesh
19 天前
@timsims 我想错了。4 楼的回答才是对的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1086383

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX