【问题标题】:Terminate the second goroutine终止第二个 goroutine
【发布时间】:2015-01-15 12:27:46
【问题描述】:

我有以下代码sn-p。

package main

import (
    "errors"
    "fmt"
    "time"
)

func errName(ch chan error) {

    for i := 0; i < 10000; i++ {

    }

    ch <- errors.New("Error name")
    close(ch)
}

func errEmail(ch chan error) {

    for i := 0; i < 100; i++ {

    }
    ch <- errors.New("Error email")
    close(ch)
}

func main() {

    ch := make(chan error)

    go errName(ch)
    go errEmail(ch)
    fmt.Println(<-ch)
    //close(ch)

    time.Sleep(1000000)

}

如你所见,我让两个函数在 goroutine 中运行,errName 和 errEmail。我将错误类型的通道作为参数传递。如果其中一个先完成,它应该通过通道发送错误并关闭它。所以第二个仍在运行的 goroutine 不必再运行了,因为我已经遇到了错误,我想终止仍在运行的 goroutine。这就是我在上面的示例中试图达到的目标。

当我运行程序时,我遇到了错误

panic: send on closed channel

goroutine 6 [running]:
main.errEmail(0xc0820101e0)
        D:/gocode/src/samples/gorountine2.go:24 +0xfd
created by main.main
        D:/gocode/src/samples/gorountine2.go:33 +0x74

goroutine 1 [runnable]:
main.main()
        D:/gocode/src/samples/gorountine2.go:34 +0xac
exit status 2

我知道,当我删除 close 语句时,它不会恐慌,但是正在运行的 goroutine 上的通道仍在等待错误引用,这意味着它浪费了内存(等待)。

当其中一个向频道发送错误时,第二个错误我将不再关心,那是我的目标。

【问题讨论】:

    标签: go


    【解决方案1】:

    组织这种行为的标准方法是使用

    package main
    
    import (
        "fmt"
        "time"
    
        "code.google.com/p/go.net/context"
    )
    
    func errName(ctx context.Context, cancel context.CancelFunc) {
        for i := 0; i < 10000; i++ {
            select {
            case <-ctx.Done():
                return
            default:
            }
        }
        cancel()
    }
    
    func errEmail(ctx context.Context, cancel context.CancelFunc) {
    
        for i := 0; i < 100; i++ {
            select {
            case <-ctx.Done():
                return
            default:
            }
        }
        cancel()
    }
    
    func main() {
    
        ctx := context.Background()
    
        ctx, cancel := context.WithCancel(ctx)
    
        go errName(ctx, cancel)
        go errEmail(ctx, cancel)
    
        <-ctx.Done()
    
        if ctx.Err() != nil {
            fmt.Println(ctx.Err())
        }
    
        time.Sleep(1000000)
    
    }
    

    你可以阅读两篇关于这个问题的好文章:

    1. http://blog.golang.org/context
    2. http://blog.golang.org/pipelines

    【讨论】:

    • 我读过这篇文章,但我不知道如何使用它。举个例子,谢谢,我会试试的。
    • 我看不懂select语句和语句
    • select 是基本操作。阅读并发教程以获取更多信息。例如:golang-book.com/10/index.htm。您也可以收听演讲:youtube.com/watch?v=f6kdp27TYZs
    • 这是通过轮询频道取消。没有办法“立即”取消/中止 goroutine。
    【解决方案2】:

    使用另一个通道表示完成:

    package main
    
    import (
        "errors"
        "fmt"
        "time"
    )
    
    func errName(ch chan error, done chan struct{}) {
        for i := 0; i < 10000; i++ {
            select {
            case <-done:
                fmt.Println("early return from name")
                return
            default:
            }
        }
        select {
        case: ch <- errors.New("Error name")
        default:
        }
    }
    
    func errEmail(ch chan error, done chan struct{}) {
        for i := 0; i < 100; i++ {
            select {
            case <-done:
                fmt.Println("early return from email")
                return
            default:
            }
        }
        select {
        case ch <- errors.New("Error email"):
        default:
        }
    }
    
    func main() {
        ch := make(chan error, 1)
        done := make(chan struct{})
        go errName(ch, done)
        go errEmail(ch, done)
        fmt.Println(<-ch)
        close(done)
        time.Sleep(1000000)
    }
    

    playground example

    为了防止丢失的 goroutine 在通道发送时永远阻塞,我创建了容量为 1 的错误通道,并在发送时使用了一个选择:

    select {
    case ch <- errors.New("Error email"):
    default:
    }
    

    如果你正在处理不止一个级别的 goroutine 完成,那么你应该考虑使用golang/x/net/context Context

    【讨论】:

      【解决方案3】:

      Done chan struct{} 提到(或它的context.Context 化身)是惯用的和真正的行为方式。但是在你的 sn-p 中避免恐慌的简单方法是

      import "sync"
      
      var once sync.Once
      
      func errName(ch chan error) {
          for i := 0; i < 10000; i++ {
      
          }
          once.Do(func() {ch <- errors.New("Error name"); close(ch)}())
      }
      func errName(ch chan error) {
          for i := 0; i < 10000; i++ {
      
          }
          once.Do(func() {ch <- errors.New("Error name"); close(ch)}())
       }
      

      【讨论】:

        猜你喜欢
        • 2023-03-10
        • 2022-09-26
        • 1970-01-01
        • 1970-01-01
        • 2017-04-10
        • 2020-11-11
        • 2021-06-23
        • 2012-09-26
        • 2011-10-12
        相关资源
        最近更新 更多