为什么这里发生了死锁?

huangapple go评论107阅读模式
英文:

Why happen here a deadlock

问题

我正在尝试理解Golang中的通道工作原理。我读了一本关于Go语言的书,找到了以下示例。

package main

import (
	"fmt"
)

// 将序列2、3、4、...发送到返回的通道
func generate() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; i <= 100; i++ {
			ch <- i
		}
	}()
	return ch
}

// 过滤掉能被'prime'整除的输入值,将剩余的值发送到返回的通道
func filter(in chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := <-in; i%prime != 0 {
				out <- i
			}
		}
	}()
	return out
}

func sieve() chan int {
	out := make(chan int)
	go func() {
		ch := generate()
		for {
			prime := <-ch
			ch = filter(ch, prime)
			out <- prime
		}
	}()
	return out
}

func main() {
	primes := sieve()
	for {
		fmt.Println(<-primes)
	}
}

当我运行这个程序时,我遇到了死锁问题,但是当我将generate函数更改为以下内容时:

// 将序列2、3、4、...发送到返回的通道
func generate() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}

然后程序将进入无限循环,但不会发生死锁。为什么当我移除for循环中的条件时会发生死锁呢?

英文:

I am trying to understand, how golang channel works. I read a book about the go language and found the following example.

package main

import (
	&quot;fmt&quot;
)

// Send the sequence 2, 3, 4, ... to returned channel 
func generate() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; i &lt;= 100 ; i++ {
			ch &lt;- i
		}
	}()
	return ch
}

// Filter out input values divisible by &#39;prime&#39;, send rest to returned channel
func filter(in chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := &lt;-in; i%prime != 0 {
				out &lt;- i
			}
		}
	}()
	return out
}

func sieve() chan int {
	out := make(chan int)
	go func() {
		ch := generate()
		for {
			prime := &lt;-ch
			ch = filter(ch, prime)
			out &lt;- prime
		}
	}()
	return out
}

func main() {
	primes := sieve()
	for {
		fmt.Println(&lt;-primes)
	}
}

When I run this programm, I've got a deadlock, but when I change the generate function to

// Send the sequence 2, 3, 4, ... to returned channel 
func generate() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch &lt;- i
		}
	}()
	return ch
}

Then the programm will run the infinite loop, but not deadlock. Why do I get deadlock, when I remove the condition in for loop?

答案1

得分: 9

阻塞原则是指在使用无缓冲通道时,当一个goroutine尝试向通道写入资源但没有等待接收资源的goroutine时,通道会锁定该goroutine并使其等待;同样地,当一个goroutine尝试从无缓冲通道读取数据但没有等待发送资源的goroutine时,通道也会锁定该goroutine并使其等待。在你的代码中,由于primes通道从未关闭,main函数一直处于阻塞状态。这是因为在第三步中,右侧的goroutine将手放入通道或执行读取操作,该goroutine也会被锁定在通道中直到交换完成。发送方没有调用close(primes)

英文:

> What do you mean with blocking principle?

You can see it illustrated in the blog post "The Nature Of Channels In Go "

for an unbuffered channel:

为什么这里发生了死锁?

<sup>(Illustration from blog post "The Nature Of Channels In Go ", written by William Kennedy, Feb. 2014)</sup>

> Unbuffered channels have no capacity and therefore require both goroutines to be ready to make any exchange.
When a goroutine attempts to write a resource to an unbuffered channel and there is no goroutine waiting to receive the resource, the channel will lock the goroutine and make it wait.
When a goroutine attempts to read from an unbuffered channel, and there is no goroutine waiting to send a resource, the channel will lock the goroutine and make it wait.

That is what happens in your case with your reader:

func main() {
    primes := sieve()
    for {
        fmt.Println(&lt;-primes)
    }
}

since primes is never closed, main remains blocked.
It (main) is in step 3:

> in step 3, the goroutine on the right places his hand into the channel or performs a read.
That goroutine is also locked in the channel until the exchange is complete.

The sender never calls close(primes).

答案2

得分: 5

让我们考虑一个更简单的例子:

func generate() chan int {
    ch := make(chan int)
    go func() {
        for i := 2; /*i < 100*/; i++ {
            ch <- i
        }
    }()
    return ch
}

func main() {
    for i := range generate() {
        fmt.Println(i)
    }
}

如果取消注释条件 i < 100,由 generate 生成的 goroutine 在发送了 98 个数字后停止。然而,它没有关闭通道,所以 main 不知道是否还会有更多的数字被发送,它只是一直在通道上阻塞。由于 main 现在是唯一存在的 goroutine(另一个已经返回),而且它被阻塞了,所以发生了死锁。

英文:

Let's consider a simpler example:

func generate() chan int {
    ch := make(chan int)
    go func() {
        for i := 2; /*i &lt; 100*/; i++ {
            ch &lt;- i
        }
    }()
    return ch
}

func main() {
    for i := range generate() {
        fmt.Println(i)
    }
}

With the condition i &lt; 100 uncommented, the goroutine spawned by generate stops after sending 98 numbers. However, it does not close the channel, so main has no way of knowing that no more numbers are going to be sent, and it just keeps blocking on the channel. Since main is now the only goroutine still in existence (the other one has returned), and it's blocking, you have a deadlock.

huangapple
  • 本文由 发表于 2014年7月23日 17:19:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/24906565.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定