尝试停止创建更多的goroutine时发生了恐慌。

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

Panic while trying to stop creating more goroutines

问题

我正在尝试并行调用一个API以加快速度,但我面临一个问题:如果我从其中一个goroutine调用中收到错误,我需要停止启动goroutine来调用API。由于我在错误处理部分和执行完成时都关闭了通道,所以我得到了一个panic: close of closed channel错误。有没有一种优雅的方式来处理这个问题,而不会导致程序崩溃?任何帮助将不胜感激!

以下是伪代码片段。

  1. for i := 0; i < someNumber; i++ {
  2. go func(num int, q chan<- bool) {
  3. value, err := callAnAPI()
  4. if err != nil {
  5. close(q)//退出for循环
  6. }
  7. // 在这里处理value
  8. wg.Done()
  9. }(i, quit)
  10. }
  11. close(quit)

为了模拟我的场景,我编写了以下程序。有没有办法在满足条件(已注释)后优雅地退出for循环?

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func receive(q <-chan bool) {
  7. for {
  8. select {
  9. case <-q:
  10. return
  11. }
  12. }
  13. }
  14. func main() {
  15. quit := make(chan bool)
  16. var result []int
  17. wg := &sync.WaitGroup{}
  18. wg.Add(10)
  19. for i := 0; i < 10; i++ {
  20. go func(num int, q chan<- bool) {
  21. //if num == 5 {
  22. // close(q)
  23. //}
  24. result = append(result, num)
  25. wg.Done()
  26. }(i, quit)
  27. }
  28. close(quit)
  29. receive(quit)
  30. wg.Wait()
  31. fmt.Printf("Result: %v", result)
  32. }
英文:

I'm trying to parallelize calls to an API to speed things up, but I'm facing a problem where I need to stop spinning up goroutines to call the API if I receive an error from one of the goroutine calls. Since I am closing the channel twice(once in the error handling part and when the execution is done), I'm getting a panic: close of closed channel error. Is there an elegant way to handle this without the program to panic? Any help would be appreciated!

The following is the pseudo-code snippet.

  1. for i := 0; i &lt; someNumber; i++ {
  2. go func(num int, q chan&lt;- bool) {
  3. value, err := callAnAPI()
  4. if err != nil {
  5. close(q)//exit from the for-loop
  6. }
  7. // process the value here
  8. wg.Done()
  9. }(i, quit)
  10. }
  11. close(quit)

To mock my scenario, I have written the following program. Is there any way to exit the for-loop gracefully once the condition(commented out) is satisfied?

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;sync&quot;
  5. )
  6. func receive(q &lt;-chan bool) {
  7. for {
  8. select {
  9. case &lt;-q:
  10. return
  11. }
  12. }
  13. }
  14. func main() {
  15. quit := make(chan bool)
  16. var result []int
  17. wg := &amp;sync.WaitGroup{}
  18. wg.Add(10)
  19. for i := 0; i &lt; 10; i++ {
  20. go func(num int, q chan&lt;- bool) {
  21. //if num == 5 {
  22. // close(q)
  23. //}
  24. result = append(result, num)
  25. wg.Done()
  26. }(i, quit)
  27. }
  28. close(quit)
  29. receive(quit)
  30. wg.Wait()
  31. fmt.Printf(&quot;Result: %v&quot;, result)
  32. }

答案1

得分: 0

你可以使用context包,该包定义了Context类型,用于在API边界和进程之间传递截止时间、取消信号和其他请求范围的值。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "sync"
  6. )
  7. func main() {
  8. ctx, cancel := context.WithCancel(context.Background())
  9. defer cancel() // 在完成后取消,即使没有错误
  10. wg := &sync.WaitGroup{}
  11. for i := 0; i < 10; i++ {
  12. wg.Add(1)
  13. go func(num int) {
  14. defer wg.Done()
  15. select {
  16. case <-ctx.Done():
  17. return // 发生错误,终止
  18. default: // 避免阻塞
  19. }
  20. // 在这里编写你的代码
  21. // res, err := callAnAPI()
  22. // if err != nil {
  23. // cancel()
  24. // return
  25. //}
  26. if num == 5 {
  27. cancel()
  28. return
  29. }
  30. fmt.Println(num)
  31. }(i)
  32. }
  33. wg.Wait()
  34. fmt.Println(ctx.Err())
  35. }

Go Playground上尝试。

你还可以查看这个答案以获取更详细的解释。

英文:

You can use context package which defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

  1. package main
  2. import (
  3. &quot;context&quot;
  4. &quot;fmt&quot;
  5. &quot;sync&quot;
  6. )
  7. func main() {
  8. ctx, cancel := context.WithCancel(context.Background())
  9. defer cancel() // cancel when we are finished, even without error
  10. wg := &amp;sync.WaitGroup{}
  11. for i := 0; i &lt; 10; i++ {
  12. wg.Add(1)
  13. go func(num int) {
  14. defer wg.Done()
  15. select {
  16. case &lt;-ctx.Done():
  17. return // Error occured somewhere, terminate
  18. default: // avoid blocking
  19. }
  20. // your code here
  21. // res, err := callAnAPI()
  22. // if err != nil {
  23. // cancel()
  24. // return
  25. //}
  26. if num == 5 {
  27. cancel()
  28. return
  29. }
  30. fmt.Println(num)
  31. }(i)
  32. }
  33. wg.Wait()
  34. fmt.Println(ctx.Err())
  35. }

Try on: Go Playground

You can also take a look to this answer for more detailed explanation.

huangapple
  • 本文由 发表于 2020年2月5日 11:29:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/60068633.html
匿名

发表评论

匿名网友

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

确定