运行时错误:goroutine永远阻塞

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

Runtime: goroutine blocks forever

问题

我有这段 Golang 代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	burstyLimiter := make(chan time.Time, 3)
	for i := 1; i <= 3; i++ {
		burstyLimiter <- time.Now()
	}

	go func() {
		for t := range time.Tick(200 * time.Millisecond) {
			burstyLimiter <- t
		}
	}()

	burstyRequests := make(chan int, 5)
	for i := 1; i <= 5; i++ {
		burstyRequests <- i
	}
	for r := range burstyRequests {
		fmt.Println("received request", r, time.Now())
	}
}

当我运行这段代码时,它会永远阻塞,如下所示:

运行时错误:goroutine永远阻塞

然后我有另一个稍微修改过的版本:

package main

import (
	"fmt"
	"time"
)

func main() {

	burstyRequests := make(chan int, 5)
	for i := 1; i <= 5; i++ {
		burstyRequests <- i
	}
	for r := range burstyRequests {
		fmt.Println("received request", r, time.Now())
	}
}

这个版本按预期会发生死锁,如下所示:

运行时错误:goroutine永远阻塞

对于永远阻塞的那个版本,我的假设是由于 time.Tick 的行为可能导致泄漏,以及对通道的范围循环会在通道有可能写入时阻塞。

我希望能够清楚地了解我对这种情况的理解是否正确。

英文:

I have this bit of Golang code here

package main

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

func main() {
	burstyLimiter := make(chan time.Time, 3)
	for i := 1; i &lt;= 3; i++ {
		burstyLimiter &lt;- time.Now()
	}

	go func() {
		for t := range time.Tick(200 * time.Millisecond) {
			burstyLimiter &lt;- t
		}
	}()

	burstyRequests := make(chan int, 5)
	for i := 1; i &lt;= 5; i++ {
		burstyRequests &lt;- i
	}
	for r := range burstyRequests {
		fmt.Println(&quot;received request&quot;, r, time.Now())
	}
}

When I run this, it blocks forever as shown here
运行时错误:goroutine永远阻塞

Then I have another slightly modified version here

package main

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

func main() {

	burstyRequests := make(chan int, 5)
	for i := 1; i &lt;= 5; i++ {
		burstyRequests &lt;- i
	}
	for r := range burstyRequests {
		fmt.Println(&quot;received request&quot;, r, time.Now())
	}
}

this version runs into a deadlock as expected as seen here
运行时错误:goroutine永远阻塞

For the one which blocks forever, my assumption is due to the behavior of time.Tick which possibly results in a leak and the range over a channel which blocks as long as there’s a possibility of a write into the channel.

I would like to get help with clarity as to whether or not my understanding of this situation is correct.​​

答案1

得分: 1

你的主要问题是设置了一个用于从通道中提取数据的for循环,只要通道是打开的,就会一直运行,而没有关闭该通道。在前一种情况下,代码块会一直阻塞,很可能是因为它在等待从burstyLimiter中提取数据,而该通道已经写入死锁。在后一种情况下,死锁足够简单,Go可以检测到并为您引发恐慌。要解决此问题,您需要做两件事:

  1. 确保通道已关闭
  2. 确保循环有退出的方式

以下是一些代码,应该可以帮助您完成这两个任务:

package main

import (
    "fmt"
    "time"
)

func main() {

    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }

    defer close(burstyRequests)
loop:
    for {
        select {
            case r := <-burstyRequests:
                fmt.Println("received request", r, time.Now())
            default:
                break loop
        }
    }
}

这段代码首先确保在函数返回时关闭了burstyLimiter,然后进入一个循环,在该循环中尝试从burstyLimiter接收数据,直到无法再接收到数据为止。这是实现您想要的功能的最基本的方法。

如果您有更复杂的用例,您可以包括另一个名为quit的通道,当您想要停止接收时向其发送,并检查是否在该通道上接收到数据,否则等待数据。或者您可以添加一个计时器。但是,无论您做什么,都需要一种方式来确保您的程序知道何时完成工作并可以关闭。

英文:

Your primary issue is that you've setup a for-loop designed to pull data out of a channel, so long as it's open, without ever closing that channel. In the former case, the code blocks forever, likely because it's waiting on data to be pulled from burstyLimiter, which is write-deadlocked. In the latter case, the deadlock is simple enough for Go to detect it and it panics for you. To fix this, you need to do two things:

  1. Ensure that the channel is closed
  2. Ensure that the loop has a way to exit

Here's some code that should help you accomplish both of these:

package main

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

func main() {

    burstyRequests := make(chan int, 5)
    for i := 1; i &lt;= 5; i++ {
        burstyRequests &lt;- i
    }

    defer close(burstyRequests)
loop:
    for {
        select {
            case r := &lt;-burstyRequests:
                fmt.Println(&quot;received request&quot;, r, time.Now())
            default:
                break loop
        }
    }
}

This code works by first ensuring that burstyLimiter is closed when the function returns and then entering a loop wherein it attempts to receive from burstyLimiter until no more data can be received. This is the most basic way to do what you want.

If you have a more complex use-case, you could include another channel called quit that you send to when you want to stop receiving and then check if you receive on that or wait for data otherwise. Or you could add a timer. But, no matter what you do, you need a way to ensure that your program knows when it is done with its work and can shut down.

huangapple
  • 本文由 发表于 2023年4月19日 04:54:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76048956.html
匿名

发表评论

匿名网友

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

确定