为什么Go语言的通道可以关闭两次?

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

Why Go's channel can close twice?

问题

当我在进行一些Go语言练习代码时,我遇到了一个问题,即一个通道可以被关闭两次,就像这样:

// jobs.go

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, playground")
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, more := <-jobs

            fmt.Println("receive close: ", j, more)
            done <- true
        }
    }()

    close(jobs)
    <-done
}

输出结果:

~ go run jobs.go
Hello, playground
receive close: 0 false
receive close: 0 false

但是,当我手动关闭通道两次时,我得到了panic: close of closed channel的错误。

为什么上面的代码会接收到两次关闭通道的操作?

英文:

When I am do some go practices code, I encounter a problem that a channel can be closed twice like this:

// jobs.go

package main

import (
   &quot;fmt&quot;
)

func main() {
    fmt.Println(&quot;Hello, playground&quot;)
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
   		for {
            j,more := &lt;-jobs
		
		    fmt.Println(&quot;receive close: &quot;, j, more)
		    done &lt;- true   
    	}
	}()

    close(jobs)
    &lt;- done
}

Output:

~ go run jobs.go
Hello, playground
receive close:  0 false
receive close:  0 false

But when I close the channel twice manually, I got panic: close of closed channel.

Why the code above can receive close twice?

答案1

得分: 10

一个通道只能关闭一次,尝试关闭已关闭的通道会导致 panic。

但是从已关闭的通道接收数据没有限制,从已关闭的通道接收数据:

从已关闭的通道进行接收操作总是可以立即进行,之前发送的值都被接收后,会返回元素类型的零值。

一个 Go 应用程序会一直运行,直到其主 goroutine 运行结束(在“正常”情况下),或者从另一个角度来看:Go 应用程序在其主 goroutine 终止时终止,也就是 main() 函数返回。它不会等待其他非 main goroutine 完成。

你使用一个无法终止的无限 for 循环启动了第二个 goroutine。因此,该循环将一直运行,直到运行在并发的主 goroutine 中的 main() 函数返回。由于 for 循环首先从 jobs 接收数据,它会等待主 goroutine 关闭它(只有在这种情况下接收操作才能继续)。然后,主 goroutine 想要从 done 接收数据,所以它会等待第二个 goroutine 在其上发送一个值。然后,主 goroutine 可以在任何时刻“自由”终止。你的第二个 goroutine 在循环中可能会从 jobs 接收到额外的值,因为它已经关闭,但是随后对 done 的发送操作会被阻塞,因为没有人再从中接收数据(而且它是无缓冲的)。

通常使用 for range 从通道接收数据直到通道关闭:

for j := range jobs {
    fmt.Println("received: ", j)
    done <- true
}

当然,在你的情况下,这会导致死锁,因为循环体永远不会执行,因为没有人在 jobs 上发送任何数据,所以循环永远不会进入其体内发送一个值到 done,而主 goroutine 正在等待这个值。

英文:

A channel can only be closed once, attempting to close a closed channel panics.

But receiving from a closed channel is not limited, receiving from a closed channel:

> A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.

A Go app runs until its main goroutine runs (given "normal" circumstances), or from another point of view: a Go app terminates when its main goroutine terminates, that is, the main() function returns. It does not wait for other non-main goroutines to complete.

You started a 2nd goroutine with an endless for loop, with no way of terminating. So that loop will keep going until the main() function –which runs in the concurrent, main goroutine– returns. Since the for loop first receives from jobs, it waits for the main goroutine to close it (this receive operation can only then proceed). Then the main goroutine wants to receive from done, so that waits until the 2nd goroutine sends a value on it. Then the main goroutine is "free" to terminate at any moment. Your 2nd goroutine running the loop may receive an additional value from jobs since it's closed, but the subsequent send on done will block as there are nobody receiving from it anymore (and it's unbuffered).

Receiving from a channel until it's closed is normally done using for range, which exits if the channel is closed:

for j := range jobs {
	fmt.Println(&quot;received: &quot;, j)
	done &lt;- true
}

Of course this would cause a deadlock in your case, as the loop body would never be reached as nobody sends anything on jobs, and so the loop would never enter its body to send a value on done which is what the main goroutine waits for.

答案2

得分: 1

jobs通道并没有被关闭两次。只有在调用close(jobs)时才关闭一次。你看到的输出是由于goroutine和主线程的执行方式造成的。

当goroutine触发时,它不会立即开始运行。相反,程序控制会转到以下部分:

  close(jobs) // <--
  <- done
}

然后jobs被关闭。主线程接下来会在对done的下一个接收操作上阻塞。

现在发生了上下文切换,goroutine开始运行。它从一个已关闭的jobs中读取数据,打印出相应的值(morefalse表示通道已关闭),并通过done发送一个true

然而,循环能够再次执行,而goroutine在对done的下一个发送操作上阻塞。此时,main再次唤醒,从done接收数据并终止程序。

英文:

The jobs channel is not being closed twice. It's closed only once when close(jobs) is called. The output you are seeing is because of how the goroutine and main threads are executing.

When the goroutine fires, it does not immidieatly start running. Instead, the program control goes to this part:

  close(jobs) // &lt;--
  &lt;- done
}

and jobs is closed. Main thread then hangs on the next receive on done.

Now context switching occurs and the goroutine starts running. It reads from a closed jobs, prints the appropriate values (false for more indicating a closed channel), and sends a true along done.

However, the loop is able to execute once more and the goroutine blocks on the next send on done. Now main wakes up again, receives on done and terminates the program.

答案3

得分: 0

Go通道不会被关闭两次。在第一次打印之后,你传递了done <- true

j, more := <-jobs
fmt.Println("receive close:", j, more)
done <- true

所以它打印了两次:

receive close: 0 false
receive close: 0 false

如果你在打印之前使用done <- true,那么它只会打印一次,并且通道会被关闭。

done <- true
j, more := <-jobs
fmt.Println("receive close:", j, more)

输出结果为:

receive close: 0 false
英文:

Go channel no getting closed twise. your are passing done <- true after first print

j,more := &lt;-jobs

fmt.Println(&quot;receive close: &quot;, j, more)
done &lt;- true

so it printed two time

receive close:  0 false
receive close:  0 false

If you use done <- true before print then it will print one time only, will get closed.

done &lt;- true
j,more := &lt;-jobs
    
fmt.Println(&quot;receive close: &quot;, j, more)

Output :

receive close:  0 false

huangapple
  • 本文由 发表于 2017年5月3日 15:12:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/43753470.html
匿名

发表评论

匿名网友

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

确定