写入通道被永久阻塞

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

Write to channel blocked forever

问题

我被卡在一个奇怪的情况中,写操作到通道中从未发生。

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int)
	s := make(chan bool)
	k := make(chan bool)
	fmt.Println("start")
	go func() {
		fmt.Println("routine 1")
		s <- true
	}()
	go func() {
		fmt.Println("routine 2")
		for {
			select {
			case <-s:
				for i := 0; i < 3; i++ {
					fmt.Println("before")
					c <- i
					fmt.Println("after")
				}
			case <-k:
				fmt.Println("k ready")
				break
			}
		}
	}()

	go func() {
		fmt.Println("routine 3")
		for {
			select {
			case x := <-c:
				fmt.Println("x=", x)
				k <- true
				fmt.Println("k done")

			}
		}
	}()

	time.Sleep(1000 * time.Millisecond)
}

以下是输出结果:

start
routine 1
routine 2
before
routine 3
x= 0
after
before

我想知道为什么写入通道k会被阻塞,但日志语句fmt.Println("k ready")从未打印出来。

这是我认为的情况:

  • 协程1向通道s写入true
  • 协程2向通道c写入0并等待,因为缓冲区大小为0,除非有人读取通道c,否则它将无法将'1'写入其中
  • 协程3进入场景,读取通道c(现在协程2可以在协程2恢复后写入c),打印x的值。现在它应该能够写入通道k,但实际上没有发生

根据我的理解,它应该能够写入通道k,然后执行协程的第2个case,并打印"k ready"。

有人能解释一下为什么写入通道被阻塞吗?
作为修复,我知道我可以增加通道c的缓冲区大小,这样就可以打印出所有内容,但我对修复此问题不感兴趣,而是想理解这种情况。

这是一个很好的博客,可以理解上述情况。

英文:

I am stuck in a strange situation where the write operation to the channel never happens.

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	c := make(chan int)
	s := make(chan bool)
	k := make(chan bool)
	fmt.Println(&quot;start&quot;)
	go func() {
	fmt.Println(&quot;routine 1&quot;)
		s &lt;- true
	}()
	go func() {
	fmt.Println(&quot;routine 2&quot;)
		for {
			select {
			case  &lt;-s :
				for i := 0; i &lt; 3; i++ {
					fmt.Println(&quot;before&quot;)
					c &lt;- i
					fmt.Println(&quot;after&quot;)
				}		
			case &lt;- k :
				fmt.Println(&quot;k ready&quot;)
				break
			}
		}
	}()

	go func() {
		fmt.Println(&quot;routine 3&quot;)
		for {
			select {
				case x := &lt;- c :
				fmt.Println(&quot;x=&quot;, x)
				k &lt;- true
				fmt.Println(&quot;k done&quot;)
			
			}
		}
	}()
	
	time.Sleep(1000 * time.Millisecond)
}

And here is the output:

start
routine 1
routine 2
before
routine 3
x= 0
after
before

I wonder why the write to the channel k blocks, but the log statement fmt.Println(&quot;k ready&quot;) is never printed.

Here is what I think :

  • go routine 1 writes true to channel s
  • go routine 2 writes 0 to
    channel c and waits because buffer size is 0, it will not be able to
    write '1' to it unless someone reads channel c
  • go routine 3 comes into the picture, reads channel c (now go routine 2
    can write to c once go routine 2 resumes) prints the value of x. NOW IT SHOULD BE ABLE TO WRITE TO CHANNEL K but that is not happening

According to me it should be able to write to channel k then case 2 of goroutine should execute and print "k ready"

Can anyone explain me why write to the channel blocked?
As a fix I know I can increase the buffer size of channel c and everything will get printed but I am not interested in fixing this, instead I want to understand this scenario.

A nice blog to understand the above case.

答案1

得分: 3

你遇到了死锁问题。

  • goroutine 1 写入 s 然后退出
  • goroutine 2 从 s 读取,并写入 c
  • goroutine 3 从 c 读取,并写入 k,但由于没有任何地方从 k 读取,所以这个操作被阻塞了,因为 goroutine 2 在上面的写入 k 操作中被阻塞了。
  • goroutine 2 再次写入 c,但由于 goroutine 3 仍在尝试写入 k,因此它不会从 c 中读取,导致阻塞。

与你所说的相反,你的缓冲区大小不是1,而是0(即无缓冲通道),因此写入操作会被阻塞,直到有其他地方进行读取。这可能是你误解的原因。根据语言规范

> 可以使用内置函数 make 创建一个新的、初始化的通道值,该函数接受通道类型和可选的容量作为参数:
>
> make(chan int, 100)
>
> 容量(元素数量)设置了通道中缓冲区的大小。如果容量为零或不存在,则通道是无缓冲的,只有在发送方和接收方都准备好时通信才会成功。否则,通道是有缓冲的,如果缓冲区不满(发送)或不空(接收),通信将成功而不会阻塞。空通道永远不会准备好进行通信。

英文:

You have a deadlock.

  • goroutine 1 writes to s then quits
  • goroutine 2 reads from s, and writes to c
  • goroutine 3 reads from c, and writes to k, and this blocks because nothing is reading from k, because goroutine 2 is blocked in the write to k above.
  • goroutine 2 writes to c again which blocks as goroutine 3 is still trying to write to k and thus is not reading from c

Contrary to what you say, you don't have a buffer size of 1. You have a buffer size of zero (i.e. an unbuffered channel), so a write will block until something reads. This is probably the source of your misunderstanding. Per the language specification:

> A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:
>
> make(chan int, 100)
>
> The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.

huangapple
  • 本文由 发表于 2017年1月22日 01:08:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/41782133.html
匿名

发表评论

匿名网友

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

确定