英文:
Changing channel from unbuffered to buffered prevents goroutine from running
问题
这是一个使用通道和选择语句的goroutine练习。如果将disconnect通道从无缓冲通道更改为有缓冲通道,goroutine将根本不会运行。
为什么从无缓冲通道更改为有缓冲通道会阻止goroutine的运行?
func SelectDemo(wg *sync.WaitGroup) {
messageCh := make(chan int, 10)
disconnectCh := make(chan struct{})
// 如果通道是有缓冲的,goroutine将不会运行
//disconnectCh := make(chan struct{}, 1)
defer close(messageCh)
defer close(disconnectCh)
go func() {
fmt.Println(" goroutine")
wg.Add(1)
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
fmt.Println(" disconnectCh")
// 在退出之前清空有缓冲的通道
for {
select {
case v := <-messageCh:
fmt.Println(v)
default:
fmt.Println(" disconnection, return")
wg.Done()
return
}
}
}
}
}()
fmt.Println("Sending ints")
for i := 0; i < 10; i++ {
messageCh <- i
}
fmt.Println("Sending done")
disconnectCh <- struct{}{}
}
以下是从主函数调用该函数的代码。我使用等待组来确保在程序退出之前goroutine完成:
wg := sync.WaitGroup{}
SelectDemo(&wg)
wg.Wait()
英文:
Here is an exercise using channels and select in a goroutine. If the disconnect channel is changed to a buffered channel the goroutine doesn't run at all.
Why does changing from an unbuffered to a buffered channel prevent running the goroutine?
func SelectDemo(wg *sync.WaitGroup) {
messageCh := make(chan int, 10)
disconnectCh := make(chan struct{})
// go routine won't run if channel is buffered
//disconnectCh := make(chan struct{}, 1)
defer close(messageCh)
defer close(disconnectCh)
go func() {
fmt.Println(" goroutine")
wg.Add(1)
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
fmt.Println(" disconnectCh")
// empty the buffered channel before exiting
for {
select {
case v := <-messageCh:
fmt.Println(v)
default:
fmt.Println(" disconnection, return")
wg.Done()
return
}
}
}
}
}()
fmt.Println("Sending ints")
for i := 0; i < 10; i++ {
messageCh <- i
}
fmt.Println("Sending done")
disconnectCh <- struct{}{}
}
Here's the code to call the function from main. I use the wait group to assure that the goroutine completes before the program exits:
wg := sync.WaitGroup{}
ch09.SelectDemo(&wg)
wg.Wait()
答案1
得分: 3
这段代码逻辑有很多缺陷,其中一些包括:
- 由于
messageCh
是有缓冲的,所以这段代码不会阻塞:
for i := 0; i < 10; i++ {
messageCh <- i
}
因此,下一段代码会在快速路径上运行:
disconnectCh <- struct{}{}
如果你将disconnectCh
也设置为有缓冲,那么这行代码也会在没有阻塞的情况下运行,并且在运行wg.Add(1)
之前,SelectDemo
函数可能已经退出。
所以:你必须将
wg.Add(1)
放在
go func() {
之前。
- 即使在
go func() {
之前加上了wg.Add(1)
,你还有:
defer close(messageCh)
defer close(disconnectCh)
这将在SelectDemo
函数返回时关闭两个通道。而且由于两个通道都准备好了,这个select
是一个随机选择:
fmt.Println(" goroutine")
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
很有可能第二个select
会一直运行下去,因为messageCh
被关闭后,通道数据读取后会永远返回0
:
case v := <-messageCh:
fmt.Println(v)
英文:
That code logic has many flaws - some of them are:
1- Since the messageCh
is buffered, this code is not blocking:
for i := 0; i < 10; i++ {
messageCh <- i
}
so the next code is in the fast path to run:
disconnectCh <- struct{}{}
if you make the disconnectCh
buffered, this line runs without blocking too, and the SelectDemo
function may exit befor running the wg.Add(1)
.
So: You must put:
wg.Add(1)
before
go func() {
2- Even with wg.Add(1)
before go func() {
code -
you have:
defer close(messageCh)
defer close(disconnectCh)
which will close both channels at SelectDemo
function return
And this select
is a random selection since both channels are ready:
fmt.Println(" goroutine")
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
and it is highly likely that the second select:
for {
select {
case v := <-messageCh:
fmt.Println(v)
default:
fmt.Println(" disconnection, return")
wg.Done()
return
}
}
will run forever since the messageCh
is closed, returning 0
forever after channel data read:
case v := <-messageCh:
fmt.Println(v)
答案2
得分: -1
程序执行速度很快
访问网址:https://pkg.go.dev/sync#WaitGroup.Add
请注意,在计数器为零时发生的具有正增量的调用必须在等待之前发生。具有负增量的调用,或者在计数器大于零时开始的具有正增量的调用,可以在任何时间发生。通常,这意味着对Add的调用应该在创建goroutine或其他等待事件的语句之前执行。如果一个WaitGroup被重用来等待几组独立的事件,新的Add调用必须在所有先前的Wait调用返回之后发生。请参考WaitGroup示例。
func SelectDemo(wg *sync.WaitGroup) {
messageCh := make(chan int, 10)
disconnectCh := make(chan struct{}, 1)
// 如果通道是有缓冲的,goroutine将不会运行
//disconnectCh := make(chan struct{}, 1)
wg.Add(1)
defer close(messageCh)
defer close(disconnectCh)
go func() {
fmt.Println(" goroutine")
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
fmt.Println(" disconnectCh")
// 在退出之前清空缓冲通道
fmt.Println(" disconnection, return")
wg.Done()
return
}
}
}()
fmt.Println("Sending ints")
for i := 0; i < 10; i++ {
messageCh <- i
}
// 延迟发送退出信号
time.Sleep(3 * time.Second)
fmt.Println("Sending done")
disconnectCh <- struct{}{}
}
我修改了你的代码
再试一次!
英文:
Program execution is fast
Visit URL: https://pkg.go.dev/sync#WaitGroup.Add
Note that calls with a positive delta that occur when the counter is zero must happen before a Wait. Calls with a negative delta, or calls with a positive delta that start when the counter is greater than zero, may happen at any time. Typically this means the calls to Add should execute before the statement creating the goroutine or other event to be waited for. If a WaitGroup is reused to wait for several independent sets of events, new Add calls must happen after all previous Wait calls have returned. See the WaitGroup example.
func SelectDemo(wg *sync.WaitGroup) {
messageCh := make(chan int, 10)
disconnectCh := make(chan struct{}, 1)
// go routine won't run if channel is buffered
//disconnectCh := make(chan struct{}, 1)
wg.Add(1)
defer close(messageCh)
defer close(disconnectCh)
go func() {
fmt.Println(" goroutine")
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
fmt.Println(" disconnectCh")
// empty the buffered channel before exiting
fmt.Println(" disconnection, return")
wg.Done()
return
}
}
}()
fmt.Println("Sending ints")
for i := 0; i < 10; i++ {
messageCh <- i
}
//Delay sending exit signal
time.Sleep(3 * time.Second)
fmt.Println("Sending done")
disconnectCh <- struct{}{}
}
I modified your code
try again!!!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论