
package main import ( "context" "errors" "fmt" "sync" "time" ) type Detail struct { ID string } func Get(ctx context.Context, id string) (*Detail, error) { //you can call this function directly time.Sleep(time.Second * 3) if id == "3" { return nil, errors.New("error id is 3") } return &Detail{ID: id}, nil } func GetAll(ctx context.Context, ids []string) (map[string]*Detail, error) { var swg sync.WaitGroup result := make(map[string]*Detail, len(ids)) detailChan := make(chan *Detail, 3) doneChan := make(chan struct{}, 1) errChan := make(chan error, 1) defer close(detailChan) defer close(doneChan) defer close(errChan) ctx, cancel := context.WithCancel(ctx) for _, value := range ids { swg.Add(1) go func(ctx context.Context, v string) { defer swg.Done() res, err := Get(ctx, v) if err != nil { fmt.Println("get error ", err, v) errChan <- err return } detailChan <- res /* select { case <-ctx.Done(): return default: detailChan <- res } */ }(ctx, value) } go func() { for value := range detailChan { fmt.Println("range ", value) result[value.ID] = value } }() go func() { swg.Wait() doneChan <- struct{}{} }() select { case err := <-errChan: fmt.Println("select error:", err) cancel() return nil, err case <-doneChan: fmt.Println("select done") } return result, nil } func main() { str := []string{"1", "2", "3", "4", "5", "6"} GetAll(context.Background(), str) fmt.Println("end") } 当执行 cancel() 的时候,会关闭 detailChan,但是 goroutine 仍然会执行,并向 detailChan 中写数据,导致 panic。
1 FinnBai Sep 19, 2019 在 goroutine 中判断下 <-ctx.Done(),收到了就 return 结束掉这个 goroutine |
3 ngnetboy OP 感觉是不是我使用 context 的姿势不对? |
4 visitant Sep 19, 2019 detailChan 在三个槽满了的情况下,第四个 id 发送给 detailChan 被 blcok,这时发生了 err 导致 cancel()函数被执行,就会导致所有 channel 关闭吧,然后 for range 再从 detailChan 读一个数据出来,导致前一个被 detailChan 满 detailChan 的写入可以执行,就会 panic 了 |
6 xkeyideal Sep 19, 2019 @ngnetboy 谁告诉你 channel 关闭之后,for range 此 channel 就不会执行了? 楼主学艺不精了,建议写个 case 测试一下 |
8 ngnetboy OP |
9 pubby Sep 19, 2019 detailChan <- res /* select { case <-ctx.Done(): return default: detailChan <- res } */ ``` default: detailChan <- res ``` 改成 ``` case detailChan<-res: ``` |
11 zhs227 Sep 19 2019 go 的设计中 channel 一定要由写入方关闭, 不能由接收方关闭。写入一个关闭的 channel 会导致 panic,可以使用 recover 恢复,但不推荐这样使用。 |
12 ngnetboy OP 有一个办法就是不关闭 channel,让 GC 自动回收资源。 |
15 iuoui Sep 19, 2019 这几个地方改一下就可以了 errChan |
18 iuoui Sep 19, 2019 这几个地方改一下就可以了,errChan 触发的时候不能马上 return,因为会触发 defer,而且 goroutine 没有退出就会 panic。 然后再 Get 方法里判断 ctx.Err==nil,并且在 detailChan 写入之前,处理一下 err==context.Canceled 情况就可以了 |
19 SAIKAII Sep 19, 2019 via Android 手机上看代码看不清,如果是像评论里说的是 for range 的问题的话,你可以改一下。改成 for ;; v, ok = <- vchan {},然后通过判断 ok 来确定是否 chan 被关闭。记得 v 和 ok 要先声明了。虽然看起来不优雅。 |