Go并发编程深度解析:从Goroutine到Channel的实战之路
Goå¹¶å‘编程深度解æžï¼šä»ŽGoroutine到Channel的实战之路引言Goè¯è¨€è‡ªè¯žç”Ÿä¹‹æ—¥èµ·ï¼Œå¹¶å‘ç¼–ç¨‹å°±æ˜¯å¶æœ€æ ¸å¿ƒçš„设计哲å¦ã€‚Rob Pikeé‚£å¥Don’t communicate by sharing memory, share memory by communicatingä¸ä»æ˜¯ä¸€å¥æ ‡è¯ï¼Œæ›´æ˜¯ä¸€ç§å¨æ–°çš„å¹¶å‘编程范å¼ã€‚然而,Goroutineå’ŒChannel虽然使用起æ¥ç®€å•,但在真实项目ä¸ç”¨å¥½å®ƒä»¬å´éœ€è¦æ·±å¥ç†è§£å¶åº•层原ç†ã€‚本文从实战出å‘,系统拆解Goå¹¶å‘ç¼–ç¨‹çš„æ ¸å¿ƒæ¦‚å¿µã€å¸¸è§æ¨¡å¼å’Œé¿å‘指å—。一ã€Goroutine:轻é‡çº§å¹¶å‘的基石1.1 与æ“ä½œç³»ç»Ÿçº¿ç¨‹çš„æœ¬è´¨åŒºåˆ«æŒ‡æ ‡OS线程Goroutineåˆå§‹æ ˆå¤§å°1-8MB2KBåˆ›å»ºæˆæœ¬1-10us~10nsåˆ‡æ¢æˆæœ¬1-10us~200nsæ•°é‡è§„æ¨¡æ•°ç™¾åˆ°æ•°åƒæ•°å万packagemainimport(fmtruntimetime)funcmain(){fmt.Printf(CPUæ ¸å¿ƒæ•°: %d\n,runtime.NumCPU())fmt.Printf(GOMAXPROCS: %d\n,runtime.GOMAXPROCS(0))start:time.Now()done:make(chanbool)fori:0;i100000;i{gofunc(nint){time.Sleep(time.Second)ifn99999{done-true}}(i)}fmt.Printf(创建10万Goroutine耗时: %v\n,time.Since(start))-done}1.2 GMP调度模型GMP模型:G(Goroutine)å°è£æ‰§è¡Œä¸Šä¸‹æ–‡ï¼›M(Machine)代表OSçº¿ç¨‹ï¼ŒçœŸæ£æ‰§è¡Œè®¡ç®—ï¼›P(Processor)代表逻辑处ç†å™¨ï¼Œç»´æŠ¤æœ¬åœ°è¿è¡Œé˜Ÿåˆ—ã€‚æ ¸å¿ƒä¼˜åŠ¿åœ¨äºŽWork Stealing机制:当æŸä¸ªP的本地队列空了,从å¶ä»–P窃å–Goroutineæ¥æ‰§è¡Œï¼Œå®žçŽ°è´Ÿè½½å‡è¡¡ã€‚二ã€Channel:Goå¹¶å‘çš„çµé‚2.1 æ— ç¼“å†²Channelçš„åŒæ¥æœºåˆ¶æ— 缓冲Channelçš„å‘é€å’ŒæŽ¥æ”¶æ˜¯åŒæ¥é˜»å¡žçš„,天然适åˆåšGoroutineé—´åŒæ¥ä¿¡å·ã€‚funcmain(){ch:make(chanstring)gofunc(){time.Sleep(2*time.Second)ch-任务完æˆ}()fmt.Println(ç‰å¾ 结果...)result:-ch// 阻塞直到收到数æ®fmt.Println(收到:,result)}2.2 有缓冲Channel的异æ¥é€šä¿¡æœ‰ç¼“冲Channel在缓冲区未满时å‘é€ä¸é˜»å¡žã€‚funcmain(){jobs:make(chanint,5)// 容é‡5fori:1;i5;i{jobs-i// ä¸é˜»å¡ž}close(jobs)forjob:rangejobs{time.Sleep(500*time.Millisecond)fmt.Printf(完æˆ: %d\n,job)}}2.3 Channelå³é—的四æ¡é»„金法则永远ä¸è¦åœ¨æŽ¥æ”¶ç«¯å³é—Channelï¼ŒæŽ¥æ”¶è€æ— 法判æ–å‘é€è€æ˜¯å¦è¿˜æœ‰æ•°æ®é‡å¤å³é—åŒä¸€ä¸ªChannel会导致panic往已å³é—çš„Channelå‘逿•°æ®ä¼španic从已å³é—çš„Channel接收ä¸é˜»å¡žï¼Œç«‹å³è¿”回零值// 安å¨å ³é—模å¼typeSafeChannelstruct{chchanintonce sync.Once}func(sc*SafeChannel)SafeClose(){sc.once.Do(func(){close(sc.ch)})}三ã€å¹¶å‘模å¼å®žæˆ˜3.1 Fan-Out/Fan-In模å¼å°†ä»»åŠ¡åˆ†å‘给多个Workerå¹¶å‘处ç†ï¼Œç»“果汇总到一个Channel。funcfanOutFanIn(){jobs:make(chanint,100)results:make(chanint,100)forw:1;w3;w{goworker(w,jobs,results)}forj:1;j9;j{jobs-j}close(jobs)fora:1;a9;a{-results}}funcworker(idint,jobs-chanint,resultschan-int){forjob:rangejobs{fmt.Printf(Worker %d - job %d\n,id,job)time.Sleep(time.Second)results-job*2}}3.2 Pipeline模å¼å°†å¤„ç†æµç¨‹æ‹†è§£ä¸ºå¤šä¸ªé˜¶æ®µï¼Œæ¯ä¸ªé˜¶æ®µé€šè¿‡Channel连接。funcpipeline(){generator:func(done-chanstruct{})-chanint{out:make(chanint)gofunc(){deferclose(out)fori:1;i10;i{select{caseout-i:case-done:return}}}()returnout}square:func(done-chanstruct{},in-chanint)-chanint{out:make(chanint)gofunc(){deferclose(out)forn:rangein{select{caseout-n*n:case-done:return}}}()returnout}done:make(chanstruct{})deferclose(done)forresult:rangesquare(done,generator(done)){fmt.Println(result)}}å››ã€å¹¶å‘原è¯çš„选择4.1 sync.Mutex vs Channelåˆ¤æ–æ ‡å‡†ï¼šå¤šä¸ªGoroutine需è¦è®¿é—®å±äº«çжæ€ï¼Œä¼˜åˆç”¨sync.Mutexï¼›Goroutine之间需è¦é€šä¿¡å’Œä¼ é€’æ•°æ®æ‰€æœ‰æƒï¼Œç”¨Channel。typeCounterstruct{mu sync.Mutex valueint}func(c*Counter)Inc(){c.mu.Lock()deferc.mu.Unlock()c.value}4.2 sync.WaitGroupvarwg sync.WaitGroupfori:1;i5;i{wg.Add(1)gofunc(idint){deferwg.Done()fmt.Printf(Goroutine %d 工作ä¸\n,id)}(i)}wg.Wait()fmt.Println(å ¨éƒ¨å®Œæˆ)4.3 context:优é›çš„å–æ¶ˆä¸Žè¶æ—¶ctx,cancel:context.WithTimeout(context.Background(),3*time.Second)defercancel()gofunc(){for{select{case-ctx.Done():returndefault:time.Sleep(500*time.Millisecond)fmt.Println(工作ä¸...)}}}()-ctx.Done()fmt.Println(è¶ æ—¶æˆ–å–æ¶ˆ:,ctx.Err())五ã€å¸¸è§å¹¶å‘陷阱与解决方案5.1 Goroutine泄æ¼å¿˜è®°ç»™Goroutine设置退出æ¡ä»¶æ˜¯æœ€å¸¸è§çš„å¹¶å‘Bug。// å±é™©ï¼šGoroutine永远阻塞funcleakyFunc(){ch:make(chanint)gofunc(){val:-ch// 永远ç‰ä¸åˆ°æ•°æ®ï¼fmt.Println(val)}()}ä¿®å¤ï¼šç¡®ä¿æ¯ä¸ªGoroutine都有明确的退出路径,使用context或done channel。5.2 é—åŒä¸çš„循环å˜é‡é™·é˜±// 错误:所有goroutineå¯èƒ½æ‰“å°ç›¸åŒå€¼fori:0;i5;i{gofunc(){fmt.Println(i)}()}// æ£ç¡®ï¼šé€šè¿‡å‚æ•°ä¼ é€’fori:0;i5;i{gofunc(nint){fmt.Println(n)}(i)}åã€æ€§èƒ½è°ƒä¼˜ä¸Žç›‘控6.1 Race DetectorGoå†ç½®çš„race detector是å‘现并å‘Bugçš„åˆ©å™¨ã€‚å¼€å‘æµ‹è¯•阶段始终å¯ç”¨ï¼šgo test -race或go run -race。6.2 pprof分æžGoroutine状æ€import_net/http/pproffuncmain(){gofunc(){http.ListenAndServe(localhost:6060,nil)}()// 业务代ç }访问http://localhost:6060/debug/pprof/goroutine查看所有Goroutine状æ€å’Œå †æ ˆã€‚总结掌æ¡Goå¹¶å‘编程需è¦ç†è§£ä¸‰ä¸ªå±‚é¢ï¼šæ€ç»´å±‚é¢ï¼Œä»Žå±äº«å†å˜é€šä¿¡è½¬å‘通信å±äº«å†å˜ï¼›å·¥å·å±‚é¢ï¼ŒGoroutineã€Channelã€syncåŒã€contextå››è€éåˆï¼›å®žè·µå±‚é¢ï¼Œç†è§£Fan-out/Fan-inã€Pipelineã€Worker Poolç‰å¸¸è§æ¨¡å¼ï¼Œé¿å¼€Goroutine泄æ¼å’Œå¾ªçޝå˜é‡é™·é˜±ã€‚çœŸæ£æŽŒæ¡Goå¹¶å‘,是在æ¯ä¸ªéœ€è¦å¹¶å‘的场景ä¸è‡ªç„¶åœ°é€‰æ‹©æœ€åˆé€‚的原è¯å’Œæ¨¡å¼ã€‚è¿™ä¸æ˜¯ä¸€æœä¸€å¤•之功,需è¦åœ¨é¡¹ç›®ä¸æŒç»å®žè·µå’Œåæ€ã€‚本文约2800å—,覆盖Goå¹¶å‘编程从基础到实战的å¨é“¾è·¯çŸ¥è¯†ã€‚