【问题标题】:Golang select statement with channels and waitgroup带有通道和等待组的 Golang 选择语句
【发布时间】:2020-07-30 22:19:34
【问题描述】:

使用 Golang 进行实验,我创建了一个带有 select 语句的函数,它监听两个通道。

我的问题是代码的行为似乎不确定 - 有时会出现恐慌,有时会成功完成。

我的期望是这段代码应该总是恐慌。它应该首先收到错误,因为它应该在 waitGroup 完成之前调度,因此在成功通道被推送到之前。

package main

import (
    "errors"
    "fmt"
    "sync"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)

    doSomething(success, errs)

    select {
    case err := <-errs:
        fmt.Println("error", err)
        panic(err)
    case <-success:
        fmt.Println("success")
    }
    fmt.Println("finished successfully")
}

func doSomething(success chan bool, errs chan error) {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        err := errors.New("Some error")
        errs <- err
    }()

    wg.Wait()
    success <- true
}

【问题讨论】:

  • 您正在频道之间开始竞速。 wg.Done 在您将错误发布到频道后立即调用。这解除了主 go 例程的阻塞。尝试在错误 之后添加 time.Sleep(100 * time.Millisecond)

标签: go


【解决方案1】:

两个通道都准备好了在 select 语句之前;所以它将通过统一的伪随机选择进行选择:

让我们替换代码中的doSomething函数调用,并将defer放在函数的末尾:

package main

import (
    "errors"
    "fmt"
    "sync"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        err := errors.New("some error")
        errs <- err
        wg.Done()
    }()

    wg.Wait()
    success <- true

    select {
    case err := <-errs:
        fmt.Println("error", err)
        panic(err)
    case <-success:
        fmt.Println("success")
    }
    fmt.Println("finished successfully")
}

正如您在上面的代码示例中看到的,主 goroutine 在 wg.Wait() 等待 wg.Done() 此时代码(几乎)在功能上等同于以下代码,并且在此处的 select 语句之前 两个通道都已准备就绪

package main

import (
    "errors"
    "fmt"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)
    errs <- errors.New("some error")
    success <- true

    select {
    case err := <-errs:
        fmt.Println(err)
    case <-success:
        fmt.Println("success")
    }
}

运行:

$ go run .
some error

$ go run .
success

Select_statements:

如果一个或多个通信可以进行,则通过统一的伪随机选择选择一个可以进行的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,“select”语句会阻塞,直到至少有一个通信可以继续。

【讨论】:

  • 这对于 select 语句来说是一个很好的答案,但是它带走了 waitGroup 的关键部分。像您所做的那样更加同步,您已经取出了 go 例程和 waitGroup。我应该如何编辑 doSomething 函数以使此代码的行为可预测?
  • @willwoo:感谢您的评论,我用一个例子说得更清楚了。
  • 有趣的是,通过调用 doSomething() 作为新的 go 例程,它始终如一地工作。
猜你喜欢
  • 2019-11-07
  • 2013-11-20
  • 1970-01-01
  • 2017-12-29
  • 2018-10-06
  • 2013-09-11
  • 1970-01-01
  • 1970-01-01
  • 2023-02-11
相关资源
最近更新 更多