结束
两种方法,一是用select+channel的方法,结合WaitGroup可以同时控制多个goroutine结束,也可以在channel中传入time.After的定时器,二是利用Context里的取消函数cancel来处理,
通信
通信实际上就是在不同goroutine间传递数据,一是用channel传递数据,二是用context中的WithValue添加数据,用Value取数据,利用ctx.Done()监控,
type favContextKey string func main() { wg := &sync.WaitGroup{} values := []string{"https://www.baidu.com/", "https://www.zhihu.com/"} ctx, cancel := context.WithCancel(context.Background()) for _, url := range values { wg.Add( 1) // 父节点向子节点传递ctx时,用WithValue绑定了一k-v对来进行数据的传递, subCtx := context.WithValue(ctx, favContextKey("url"), url) go reqURL(subCtx, wg) } go func() { time.Sleep(time.Second * 3 ) cancel() }() wg.Wait() fmt.Println("exit main goroutine") } func reqURL(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() // 利用Value函数从传入的ctx中取出值,进而实现了协程通信, url, _ := ctx.Value(favContextKey("url")).(string) for { select { // 根节点取消的时候,这个Done里面的chan会close,case就会收到消息, case <-ctx.Done(): fmt.Printf("stop getting url:%s ", url) return default: r, err := http.Get(url) if r.StatusCode == http.StatusOK && err == nil { body, _ := ioutil.ReadAll(r.Body) subCtx := context.WithValue(ctx, favContextKey("resp"), fmt.Sprintf("%s%x", url, md5.Sum(body))) wg.Add(1 ) go showResp(subCtx, wg) } r.Body.Close() //启动子goroutine是为了不阻塞当前goroutine,这里在实际场景中可以去执行其他逻辑,这里为了方便直接sleep一秒 // doSometing() time.Sleep(time.Second * 1 ) } } } func showResp(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("stop showing resp") return default: //子goroutine里一般会处理一些IO任务,如读写数据库或者rpc调用,这里为了方便直接把数据打印 fmt.Println("printing ", ctx.Value(favContextKey("resp"))) time.Sleep(time.Second * 1 ) } } }