这是 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
在主线程中扮演怎样的角色
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.