因为如果不使用带缓冲的channel会导致死锁问题,死锁的原因是当其中一个协程打印结束后,即执行完以后,另外一个协程就不能在向channel写数据了,导致死锁。
例如:
n = 3时,
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,只有当读写同时就绪时才不会阻塞;两个协程的逻辑是一样的,会同时进入各自的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的逻辑不同,它们可以独立执行而不需要交替的同步。
因篇幅问题不能全部显示,请点此查看更多更全内容