终止第二个goroutine

时间:2015-01-14 16:05:49

标签: go

我有以下代码段。

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上的通道仍在等待错误引用,这就是说,它浪费了内存(等待)。

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

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分: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

答案 2 :(得分:1)

提到的

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