终止第二个 goroutine

Terminate the second goroutine

我有以下代码片段。

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。我将错误类型的通道作为参数传递。如果其中一个首先完成,它应该通过通道发送错误并关闭它。所以第二个,仍然是 运行ning goroutine,不必再 运行,因为我已经得到了错误,我想终止仍然是 运行ning 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 语句时,它不会 panic,但是 运行ning goroutine 上的通道仍在等待错误引用,这意味着,它白白浪费了内存(等待) .

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

使用另一个渠道发出完成信号:

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:

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

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

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

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

Done chan struct{} 提到(或其 context.Context 化身)是惯用的和真正的行为方式。但是避免代码段恐慌的简单方法可以是

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)}())
 }