[{"data":1,"prerenderedAt":2893},["ShallowReactive",2],{"page-\u002Fgo\u002F16-goroutines":3},{"id":4,"title":5,"body":6,"description":2886,"extension":2887,"meta":2888,"navigation":144,"path":2889,"seo":2890,"stem":2891,"__hash__":2892},"content\u002Fgo\u002F16-goroutines.md","16 — Goroutines",{"type":7,"value":8,"toc":2881},"minimark",[9,13,18,2877],[10,11,5],"h1",{"id":12},"_16-goroutines",[14,15,17],"h2",{"id":16},"the-g-m-p-scheduling-model","The G-M-P Scheduling Model",[19,20,22,236,240,2817,2864,2868],"code-wrapper",{"language":21},"go",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-go shiki shiki-themes github-light github-dark","\u002F\u002F ┌──────────────────────────────────────────────────────────────────────┐\n\u002F\u002F │ Go's scheduler uses a G-M-P model:                                   │\n\u002F\u002F │                                                                      │\n\u002F\u002F │   G (Goroutine)  — a goroutine (your `go func()`)                    │\n\u002F\u002F │   M (Machine)    — an OS thread (managed by the runtime)             │\n\u002F\u002F │   P (Processor)  — a logical processor (GOMAXPROCS of these)         │\n\u002F\u002F │                                                                      │\n\u002F\u002F │   Each P has a local run queue of Gs.                                │\n\u002F\u002F │   M executes Gs from its P's queue.                                  │\n\u002F\u002F │   When a G blocks (syscall, channel), the M is parked and the P      │\n\u002F\u002F │   is handed to another M to keep running Gs.                         │\n\u002F\u002F │                                                                      │\n\u002F\u002F │   Work stealing: if a P's queue is empty, it steals Gs from other Ps.│\n\u002F\u002F │   This keeps all CPUs busy without OS-level thread management.       │\n\u002F\u002F │                                                                      │\n\u002F\u002F │   Goroutine stack: starts at 2KB (Go 1.4+), grows on demand.         │\n\u002F\u002F │   You can have millions of goroutines (each ~2-8KB).                 │\n\u002F\u002F └──────────────────────────────────────────────────────────────────────┘\n\nfunc schedulingModel() {\n    \u002F\u002F GOMAXPROCS = number of Ps = concurrent CPU-bound goroutines.\n    \u002F\u002F Default = runtime.NumCPU().\n    fmt.Println(\"GOMAXPROCS:\", runtime.GOMAXPROCS(0))  \u002F\u002F e.g., 8 on 8-core\n\n    \u002F\u002F I\u002FO-bound goroutines block on syscalls — the M parks, the P gets\n    \u002F\u002F a new M. So you can have MORE goroutines than GOMAXPROCS (they\n    \u002F\u002F block on I\u002FO, yielding the thread).\n}\n","",[29,30,31,40,46,52,58,64,70,75,81,87,93,99,104,110,116,121,127,133,139,146,161,167,173,207,212,218,224,230],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","\u002F\u002F ┌──────────────────────────────────────────────────────────────────────┐\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"\u002F\u002F │ Go's scheduler uses a G-M-P model:                                   │\n",[32,47,49],{"class":34,"line":48},3,[32,50,51],{"class":38},"\u002F\u002F │                                                                      │\n",[32,53,55],{"class":34,"line":54},4,[32,56,57],{"class":38},"\u002F\u002F │   G (Goroutine)  — a goroutine (your `go func()`)                    │\n",[32,59,61],{"class":34,"line":60},5,[32,62,63],{"class":38},"\u002F\u002F │   M (Machine)    — an OS thread (managed by the runtime)             │\n",[32,65,67],{"class":34,"line":66},6,[32,68,69],{"class":38},"\u002F\u002F │   P (Processor)  — a logical processor (GOMAXPROCS of these)         │\n",[32,71,73],{"class":34,"line":72},7,[32,74,51],{"class":38},[32,76,78],{"class":34,"line":77},8,[32,79,80],{"class":38},"\u002F\u002F │   Each P has a local run queue of Gs.                                │\n",[32,82,84],{"class":34,"line":83},9,[32,85,86],{"class":38},"\u002F\u002F │   M executes Gs from its P's queue.                                  │\n",[32,88,90],{"class":34,"line":89},10,[32,91,92],{"class":38},"\u002F\u002F │   When a G blocks (syscall, channel), the M is parked and the P      │\n",[32,94,96],{"class":34,"line":95},11,[32,97,98],{"class":38},"\u002F\u002F │   is handed to another M to keep running Gs.                         │\n",[32,100,102],{"class":34,"line":101},12,[32,103,51],{"class":38},[32,105,107],{"class":34,"line":106},13,[32,108,109],{"class":38},"\u002F\u002F │   Work stealing: if a P's queue is empty, it steals Gs from other Ps.│\n",[32,111,113],{"class":34,"line":112},14,[32,114,115],{"class":38},"\u002F\u002F │   This keeps all CPUs busy without OS-level thread management.       │\n",[32,117,119],{"class":34,"line":118},15,[32,120,51],{"class":38},[32,122,124],{"class":34,"line":123},16,[32,125,126],{"class":38},"\u002F\u002F │   Goroutine stack: starts at 2KB (Go 1.4+), grows on demand.         │\n",[32,128,130],{"class":34,"line":129},17,[32,131,132],{"class":38},"\u002F\u002F │   You can have millions of goroutines (each ~2-8KB).                 │\n",[32,134,136],{"class":34,"line":135},18,[32,137,138],{"class":38},"\u002F\u002F └──────────────────────────────────────────────────────────────────────┘\n",[32,140,142],{"class":34,"line":141},19,[32,143,145],{"emptyLinePlaceholder":144},true,"\n",[32,147,149,153,157],{"class":34,"line":148},20,[32,150,152],{"class":151},"svdQ7","func",[32,154,156],{"class":155},"sIsaT"," schedulingModel",[32,158,160],{"class":159},"ssxIu","() {\n",[32,162,164],{"class":34,"line":163},21,[32,165,166],{"class":38},"    \u002F\u002F GOMAXPROCS = number of Ps = concurrent CPU-bound goroutines.\n",[32,168,170],{"class":34,"line":169},22,[32,171,172],{"class":38},"    \u002F\u002F Default = runtime.NumCPU().\n",[32,174,176,179,182,185,189,192,195,197,201,204],{"class":34,"line":175},23,[32,177,178],{"class":159},"    fmt.",[32,180,181],{"class":155},"Println",[32,183,184],{"class":159},"(",[32,186,188],{"class":187},"sJ6F3","\"GOMAXPROCS:\"",[32,190,191],{"class":159},", runtime.",[32,193,194],{"class":155},"GOMAXPROCS",[32,196,184],{"class":159},[32,198,200],{"class":199},"snvgF","0",[32,202,203],{"class":159},"))  ",[32,205,206],{"class":38},"\u002F\u002F e.g., 8 on 8-core\n",[32,208,210],{"class":34,"line":209},24,[32,211,145],{"emptyLinePlaceholder":144},[32,213,215],{"class":34,"line":214},25,[32,216,217],{"class":38},"    \u002F\u002F I\u002FO-bound goroutines block on syscalls — the M parks, the P gets\n",[32,219,221],{"class":34,"line":220},26,[32,222,223],{"class":38},"    \u002F\u002F a new M. So you can have MORE goroutines than GOMAXPROCS (they\n",[32,225,227],{"class":34,"line":226},27,[32,228,229],{"class":38},"    \u002F\u002F block on I\u002FO, yielding the thread).\n",[32,231,233],{"class":34,"line":232},28,[32,234,235],{"class":159},"}\n",[14,237,239],{"id":238},"starting-goroutines","Starting Goroutines",[19,241,242,551,555],{"language":21},[23,243,245],{"className":25,"code":244,"language":21,"meta":27,"style":27},"\u002F\u002F `go f(args)` starts f in a new goroutine and returns immediately.\n\u002F\u002F The caller continues while f runs concurrently.\n\nfunc fetchURL(url string, wg *sync.WaitGroup) {\n    defer wg.Done()\n    resp, err := http.Get(url)\n    if err != nil {\n        log.Printf(\"fetch %s: %v\", url, err)\n        return\n    }\n    defer resp.Body.Close()\n    \u002F\u002F process resp\n}\n\nfunc main() {\n    urls := []string{\"https:\u002F\u002Fa.com\", \"https:\u002F\u002Fb.com\", \"https:\u002F\u002Fc.com\"}\n    var wg sync.WaitGroup\n    for _, url := range urls {\n        wg.Add(1)\n        go fetchURL(url, &wg)  \u002F\u002F launch one goroutine per URL\n    }\n    wg.Wait()  \u002F\u002F block until all fetches complete\n}\n\n\u002F\u002F ⚠️ The main goroutine can exit before others finish — all goroutines\n\u002F\u002F are killed when main returns. ALWAYS synchronize (WaitGroup, channel).\n",[29,246,247,252,257,261,298,312,329,346,374,379,384,396,401,405,409,418,449,464,480,496,515,519,533,537,541,546],{"__ignoreMap":27},[32,248,249],{"class":34,"line":35},[32,250,251],{"class":38},"\u002F\u002F `go f(args)` starts f in a new goroutine and returns immediately.\n",[32,253,254],{"class":34,"line":42},[32,255,256],{"class":38},"\u002F\u002F The caller continues while f runs concurrently.\n",[32,258,259],{"class":34,"line":48},[32,260,145],{"emptyLinePlaceholder":144},[32,262,263,265,268,270,274,277,280,283,286,289,292,295],{"class":34,"line":54},[32,264,152],{"class":151},[32,266,267],{"class":155}," fetchURL",[32,269,184],{"class":159},[32,271,273],{"class":272},"sCrzJ","url",[32,275,276],{"class":151}," string",[32,278,279],{"class":159},", ",[32,281,282],{"class":272},"wg",[32,284,285],{"class":151}," *",[32,287,288],{"class":155},"sync",[32,290,291],{"class":159},".",[32,293,294],{"class":155},"WaitGroup",[32,296,297],{"class":159},") {\n",[32,299,300,303,306,309],{"class":34,"line":60},[32,301,302],{"class":151},"    defer",[32,304,305],{"class":159}," wg.",[32,307,308],{"class":155},"Done",[32,310,311],{"class":159},"()\n",[32,313,314,317,320,323,326],{"class":34,"line":66},[32,315,316],{"class":159},"    resp, err ",[32,318,319],{"class":151},":=",[32,321,322],{"class":159}," http.",[32,324,325],{"class":155},"Get",[32,327,328],{"class":159},"(url)\n",[32,330,331,334,337,340,343],{"class":34,"line":72},[32,332,333],{"class":151},"    if",[32,335,336],{"class":159}," err ",[32,338,339],{"class":151},"!=",[32,341,342],{"class":199}," nil",[32,344,345],{"class":159}," {\n",[32,347,348,351,354,356,359,362,365,368,371],{"class":34,"line":77},[32,349,350],{"class":159},"        log.",[32,352,353],{"class":155},"Printf",[32,355,184],{"class":159},[32,357,358],{"class":187},"\"fetch ",[32,360,361],{"class":199},"%s",[32,363,364],{"class":187},": ",[32,366,367],{"class":199},"%v",[32,369,370],{"class":187},"\"",[32,372,373],{"class":159},", url, err)\n",[32,375,376],{"class":34,"line":83},[32,377,378],{"class":151},"        return\n",[32,380,381],{"class":34,"line":89},[32,382,383],{"class":159},"    }\n",[32,385,386,388,391,394],{"class":34,"line":95},[32,387,302],{"class":151},[32,389,390],{"class":159}," resp.Body.",[32,392,393],{"class":155},"Close",[32,395,311],{"class":159},[32,397,398],{"class":34,"line":101},[32,399,400],{"class":38},"    \u002F\u002F process resp\n",[32,402,403],{"class":34,"line":106},[32,404,235],{"class":159},[32,406,407],{"class":34,"line":112},[32,408,145],{"emptyLinePlaceholder":144},[32,410,411,413,416],{"class":34,"line":118},[32,412,152],{"class":151},[32,414,415],{"class":155}," main",[32,417,160],{"class":159},[32,419,420,423,425,428,431,434,437,439,442,444,447],{"class":34,"line":123},[32,421,422],{"class":159},"    urls ",[32,424,319],{"class":151},[32,426,427],{"class":159}," []",[32,429,430],{"class":151},"string",[32,432,433],{"class":159},"{",[32,435,436],{"class":187},"\"https:\u002F\u002Fa.com\"",[32,438,279],{"class":159},[32,440,441],{"class":187},"\"https:\u002F\u002Fb.com\"",[32,443,279],{"class":159},[32,445,446],{"class":187},"\"https:\u002F\u002Fc.com\"",[32,448,235],{"class":159},[32,450,451,454,457,459,461],{"class":34,"line":129},[32,452,453],{"class":151},"    var",[32,455,456],{"class":159}," wg ",[32,458,288],{"class":155},[32,460,291],{"class":159},[32,462,463],{"class":155},"WaitGroup\n",[32,465,466,469,472,474,477],{"class":34,"line":135},[32,467,468],{"class":151},"    for",[32,470,471],{"class":159}," _, url ",[32,473,319],{"class":151},[32,475,476],{"class":151}," range",[32,478,479],{"class":159}," urls {\n",[32,481,482,485,488,490,493],{"class":34,"line":141},[32,483,484],{"class":159},"        wg.",[32,486,487],{"class":155},"Add",[32,489,184],{"class":159},[32,491,492],{"class":199},"1",[32,494,495],{"class":159},")\n",[32,497,498,501,503,506,509,512],{"class":34,"line":148},[32,499,500],{"class":151},"        go",[32,502,267],{"class":155},[32,504,505],{"class":159},"(url, ",[32,507,508],{"class":151},"&",[32,510,511],{"class":159},"wg)  ",[32,513,514],{"class":38},"\u002F\u002F launch one goroutine per URL\n",[32,516,517],{"class":34,"line":163},[32,518,383],{"class":159},[32,520,521,524,527,530],{"class":34,"line":169},[32,522,523],{"class":159},"    wg.",[32,525,526],{"class":155},"Wait",[32,528,529],{"class":159},"()  ",[32,531,532],{"class":38},"\u002F\u002F block until all fetches complete\n",[32,534,535],{"class":34,"line":175},[32,536,235],{"class":159},[32,538,539],{"class":34,"line":209},[32,540,145],{"emptyLinePlaceholder":144},[32,542,543],{"class":34,"line":214},[32,544,545],{"class":38},"\u002F\u002F ⚠️ The main goroutine can exit before others finish — all goroutines\n",[32,547,548],{"class":34,"line":220},[32,549,550],{"class":38},"\u002F\u002F are killed when main returns. ALWAYS synchronize (WaitGroup, channel).\n",[14,552,554],{"id":553},"the-main-exit-problem","The Main Exit Problem",[19,556,557,800,804],{"language":21},[23,558,560],{"className":25,"code":559,"language":21,"meta":27,"style":27},"\u002F\u002F ❌ ANTI-PATTERN: no synchronization — goroutine may not run\nfunc badMain() {\n    go func() {\n        fmt.Println(\"hello from goroutine\")  \u002F\u002F might never print!\n    }()\n    \u002F\u002F main returns immediately — the goroutine is killed.\n}\n\n\u002F\u002F ✅ CORRECT: WaitGroup — block until goroutines finish\nfunc goodMain() {\n    var wg sync.WaitGroup\n    wg.Add(1)\n    go func() {\n        defer wg.Done()\n        fmt.Println(\"hello from goroutine\")\n    }()\n    wg.Wait()  \u002F\u002F blocks until Done is called\n}\n\n\u002F\u002F ✅ CORRECT: channel — block until signal received\nfunc channelMain() {\n    done := make(chan struct{})\n    go func() {\n        fmt.Println(\"working...\")\n        close(done)  \u002F\u002F signal completion\n    }()\n    \u003C-done  \u002F\u002F block until done is closed\n}\n",[29,561,562,567,576,586,604,609,614,618,622,627,636,648,660,668,679,691,695,706,710,714,719,728,749,757,770,781,785,796],{"__ignoreMap":27},[32,563,564],{"class":34,"line":35},[32,565,566],{"class":38},"\u002F\u002F ❌ ANTI-PATTERN: no synchronization — goroutine may not run\n",[32,568,569,571,574],{"class":34,"line":42},[32,570,152],{"class":151},[32,572,573],{"class":155}," badMain",[32,575,160],{"class":159},[32,577,578,581,584],{"class":34,"line":48},[32,579,580],{"class":151},"    go",[32,582,583],{"class":151}," func",[32,585,160],{"class":159},[32,587,588,591,593,595,598,601],{"class":34,"line":54},[32,589,590],{"class":159},"        fmt.",[32,592,181],{"class":155},[32,594,184],{"class":159},[32,596,597],{"class":187},"\"hello from goroutine\"",[32,599,600],{"class":159},")  ",[32,602,603],{"class":38},"\u002F\u002F might never print!\n",[32,605,606],{"class":34,"line":60},[32,607,608],{"class":159},"    }()\n",[32,610,611],{"class":34,"line":66},[32,612,613],{"class":38},"    \u002F\u002F main returns immediately — the goroutine is killed.\n",[32,615,616],{"class":34,"line":72},[32,617,235],{"class":159},[32,619,620],{"class":34,"line":77},[32,621,145],{"emptyLinePlaceholder":144},[32,623,624],{"class":34,"line":83},[32,625,626],{"class":38},"\u002F\u002F ✅ CORRECT: WaitGroup — block until goroutines finish\n",[32,628,629,631,634],{"class":34,"line":89},[32,630,152],{"class":151},[32,632,633],{"class":155}," goodMain",[32,635,160],{"class":159},[32,637,638,640,642,644,646],{"class":34,"line":95},[32,639,453],{"class":151},[32,641,456],{"class":159},[32,643,288],{"class":155},[32,645,291],{"class":159},[32,647,463],{"class":155},[32,649,650,652,654,656,658],{"class":34,"line":101},[32,651,523],{"class":159},[32,653,487],{"class":155},[32,655,184],{"class":159},[32,657,492],{"class":199},[32,659,495],{"class":159},[32,661,662,664,666],{"class":34,"line":106},[32,663,580],{"class":151},[32,665,583],{"class":151},[32,667,160],{"class":159},[32,669,670,673,675,677],{"class":34,"line":112},[32,671,672],{"class":151},"        defer",[32,674,305],{"class":159},[32,676,308],{"class":155},[32,678,311],{"class":159},[32,680,681,683,685,687,689],{"class":34,"line":118},[32,682,590],{"class":159},[32,684,181],{"class":155},[32,686,184],{"class":159},[32,688,597],{"class":187},[32,690,495],{"class":159},[32,692,693],{"class":34,"line":123},[32,694,608],{"class":159},[32,696,697,699,701,703],{"class":34,"line":129},[32,698,523],{"class":159},[32,700,526],{"class":155},[32,702,529],{"class":159},[32,704,705],{"class":38},"\u002F\u002F blocks until Done is called\n",[32,707,708],{"class":34,"line":135},[32,709,235],{"class":159},[32,711,712],{"class":34,"line":141},[32,713,145],{"emptyLinePlaceholder":144},[32,715,716],{"class":34,"line":148},[32,717,718],{"class":38},"\u002F\u002F ✅ CORRECT: channel — block until signal received\n",[32,720,721,723,726],{"class":34,"line":163},[32,722,152],{"class":151},[32,724,725],{"class":155}," channelMain",[32,727,160],{"class":159},[32,729,730,733,735,738,740,743,746],{"class":34,"line":169},[32,731,732],{"class":159},"    done ",[32,734,319],{"class":151},[32,736,737],{"class":155}," make",[32,739,184],{"class":159},[32,741,742],{"class":151},"chan",[32,744,745],{"class":151}," struct",[32,747,748],{"class":159},"{})\n",[32,750,751,753,755],{"class":34,"line":175},[32,752,580],{"class":151},[32,754,583],{"class":151},[32,756,160],{"class":159},[32,758,759,761,763,765,768],{"class":34,"line":209},[32,760,590],{"class":159},[32,762,181],{"class":155},[32,764,184],{"class":159},[32,766,767],{"class":187},"\"working...\"",[32,769,495],{"class":159},[32,771,772,775,778],{"class":34,"line":214},[32,773,774],{"class":155},"        close",[32,776,777],{"class":159},"(done)  ",[32,779,780],{"class":38},"\u002F\u002F signal completion\n",[32,782,783],{"class":34,"line":220},[32,784,608],{"class":159},[32,786,787,790,793],{"class":34,"line":226},[32,788,789],{"class":151},"    \u003C-",[32,791,792],{"class":159},"done  ",[32,794,795],{"class":38},"\u002F\u002F block until done is closed\n",[32,797,798],{"class":34,"line":232},[32,799,235],{"class":159},[14,801,803],{"id":802},"goroutine-leaks-detection-and-prevention","Goroutine Leaks — Detection and Prevention",[19,805,806,1332,1339],{"language":21},[23,807,809],{"className":25,"code":808,"language":21,"meta":27,"style":27},"\u002F\u002F A leaked goroutine is one that's blocked forever and can't be reached.\n\u002F\u002F It stays alive, consuming memory (stack + any captured variables).\n\n\u002F\u002F ❌ LEAK: goroutine blocks forever — no one sends to ch\nfunc leakyFunc() {\n    ch := make(chan int)  \u002F\u002F unbuffered\n    go func() {\n        val := \u003C-ch  \u002F\u002F blocks forever — no sender\n        fmt.Println(val)\n    }()\n    \u002F\u002F leakyFunc returns; the goroutine is stuck, never freed.\n}\n\n\u002F\u002F ✅ FIX: pass a context so the goroutine can exit\nfunc nonLeaky(ctx context.Context) \u003C-chan int {\n    ch := make(chan int, 1)\n    go func() {\n        defer close(ch)\n        select {\n        case ch \u003C- compute():\n        case \u003C-ctx.Done():  \u002F\u002F exit path — prevents leak\n            return\n        }\n    }()\n    return ch\n}\n\n\u002F\u002F ❌ LEAK: goroutine blocks on send — no receiver\nfunc leakyProducer() {\n    ch := make(chan int)  \u002F\u002F unbuffered\n    go func() {\n        for i := 0; i \u003C 1000; i++ {\n            ch \u003C- i  \u002F\u002F blocks if no one receives — leak\n        }\n    }()\n    \u002F\u002F receiver never starts → goroutine stuck on first send\n}\n\n\u002F\u002F ✅ FIX: buffered channel + context, or select with ctx.Done\nfunc safeProducer(ctx context.Context) \u003C-chan int {\n    ch := make(chan int, 100)  \u002F\u002F buffer absorbs bursts\n    go func() {\n        defer close(ch)\n        for i := 0; ; i++ {\n            select {\n            case ch \u003C- i:\n            case \u003C-ctx.Done():\n                return  \u002F\u002F exit when cancelled\n            }\n        }\n    }()\n    return ch\n}\n",[29,810,811,816,821,825,830,839,860,868,884,893,897,902,906,910,915,945,965,973,983,990,1007,1024,1029,1034,1038,1046,1050,1054,1059,1069,1088,1097,1128,1142,1147,1152,1158,1163,1168,1174,1200,1225,1234,1243,1261,1269,1282,1295,1304,1310,1315,1320,1327],{"__ignoreMap":27},[32,812,813],{"class":34,"line":35},[32,814,815],{"class":38},"\u002F\u002F A leaked goroutine is one that's blocked forever and can't be reached.\n",[32,817,818],{"class":34,"line":42},[32,819,820],{"class":38},"\u002F\u002F It stays alive, consuming memory (stack + any captured variables).\n",[32,822,823],{"class":34,"line":48},[32,824,145],{"emptyLinePlaceholder":144},[32,826,827],{"class":34,"line":54},[32,828,829],{"class":38},"\u002F\u002F ❌ LEAK: goroutine blocks forever — no one sends to ch\n",[32,831,832,834,837],{"class":34,"line":60},[32,833,152],{"class":151},[32,835,836],{"class":155}," leakyFunc",[32,838,160],{"class":159},[32,840,841,844,846,848,850,852,855,857],{"class":34,"line":66},[32,842,843],{"class":159},"    ch ",[32,845,319],{"class":151},[32,847,737],{"class":155},[32,849,184],{"class":159},[32,851,742],{"class":151},[32,853,854],{"class":151}," int",[32,856,600],{"class":159},[32,858,859],{"class":38},"\u002F\u002F unbuffered\n",[32,861,862,864,866],{"class":34,"line":72},[32,863,580],{"class":151},[32,865,583],{"class":151},[32,867,160],{"class":159},[32,869,870,873,875,878,881],{"class":34,"line":77},[32,871,872],{"class":159},"        val ",[32,874,319],{"class":151},[32,876,877],{"class":151}," \u003C-",[32,879,880],{"class":159},"ch  ",[32,882,883],{"class":38},"\u002F\u002F blocks forever — no sender\n",[32,885,886,888,890],{"class":34,"line":83},[32,887,590],{"class":159},[32,889,181],{"class":155},[32,891,892],{"class":159},"(val)\n",[32,894,895],{"class":34,"line":89},[32,896,608],{"class":159},[32,898,899],{"class":34,"line":95},[32,900,901],{"class":38},"    \u002F\u002F leakyFunc returns; the goroutine is stuck, never freed.\n",[32,903,904],{"class":34,"line":101},[32,905,235],{"class":159},[32,907,908],{"class":34,"line":106},[32,909,145],{"emptyLinePlaceholder":144},[32,911,912],{"class":34,"line":112},[32,913,914],{"class":38},"\u002F\u002F ✅ FIX: pass a context so the goroutine can exit\n",[32,916,917,919,922,924,927,930,932,935,938,941,943],{"class":34,"line":118},[32,918,152],{"class":151},[32,920,921],{"class":155}," nonLeaky",[32,923,184],{"class":159},[32,925,926],{"class":272},"ctx",[32,928,929],{"class":155}," context",[32,931,291],{"class":159},[32,933,934],{"class":155},"Context",[32,936,937],{"class":159},") ",[32,939,940],{"class":151},"\u003C-chan",[32,942,854],{"class":151},[32,944,345],{"class":159},[32,946,947,949,951,953,955,957,959,961,963],{"class":34,"line":123},[32,948,843],{"class":159},[32,950,319],{"class":151},[32,952,737],{"class":155},[32,954,184],{"class":159},[32,956,742],{"class":151},[32,958,854],{"class":151},[32,960,279],{"class":159},[32,962,492],{"class":199},[32,964,495],{"class":159},[32,966,967,969,971],{"class":34,"line":129},[32,968,580],{"class":151},[32,970,583],{"class":151},[32,972,160],{"class":159},[32,974,975,977,980],{"class":34,"line":135},[32,976,672],{"class":151},[32,978,979],{"class":155}," close",[32,981,982],{"class":159},"(ch)\n",[32,984,985,988],{"class":34,"line":141},[32,986,987],{"class":151},"        select",[32,989,345],{"class":159},[32,991,992,995,998,1001,1004],{"class":34,"line":148},[32,993,994],{"class":151},"        case",[32,996,997],{"class":159}," ch ",[32,999,1000],{"class":151},"\u003C-",[32,1002,1003],{"class":155}," compute",[32,1005,1006],{"class":159},"():\n",[32,1008,1009,1011,1013,1016,1018,1021],{"class":34,"line":163},[32,1010,994],{"class":151},[32,1012,877],{"class":151},[32,1014,1015],{"class":159},"ctx.",[32,1017,308],{"class":155},[32,1019,1020],{"class":159},"():  ",[32,1022,1023],{"class":38},"\u002F\u002F exit path — prevents leak\n",[32,1025,1026],{"class":34,"line":169},[32,1027,1028],{"class":151},"            return\n",[32,1030,1031],{"class":34,"line":175},[32,1032,1033],{"class":159},"        }\n",[32,1035,1036],{"class":34,"line":209},[32,1037,608],{"class":159},[32,1039,1040,1043],{"class":34,"line":214},[32,1041,1042],{"class":151},"    return",[32,1044,1045],{"class":159}," ch\n",[32,1047,1048],{"class":34,"line":220},[32,1049,235],{"class":159},[32,1051,1052],{"class":34,"line":226},[32,1053,145],{"emptyLinePlaceholder":144},[32,1055,1056],{"class":34,"line":232},[32,1057,1058],{"class":38},"\u002F\u002F ❌ LEAK: goroutine blocks on send — no receiver\n",[32,1060,1062,1064,1067],{"class":34,"line":1061},29,[32,1063,152],{"class":151},[32,1065,1066],{"class":155}," leakyProducer",[32,1068,160],{"class":159},[32,1070,1072,1074,1076,1078,1080,1082,1084,1086],{"class":34,"line":1071},30,[32,1073,843],{"class":159},[32,1075,319],{"class":151},[32,1077,737],{"class":155},[32,1079,184],{"class":159},[32,1081,742],{"class":151},[32,1083,854],{"class":151},[32,1085,600],{"class":159},[32,1087,859],{"class":38},[32,1089,1091,1093,1095],{"class":34,"line":1090},31,[32,1092,580],{"class":151},[32,1094,583],{"class":151},[32,1096,160],{"class":159},[32,1098,1100,1103,1106,1108,1111,1114,1117,1120,1123,1126],{"class":34,"line":1099},32,[32,1101,1102],{"class":151},"        for",[32,1104,1105],{"class":159}," i ",[32,1107,319],{"class":151},[32,1109,1110],{"class":199}," 0",[32,1112,1113],{"class":159},"; i ",[32,1115,1116],{"class":151},"\u003C",[32,1118,1119],{"class":199}," 1000",[32,1121,1122],{"class":159},"; i",[32,1124,1125],{"class":151},"++",[32,1127,345],{"class":159},[32,1129,1131,1134,1136,1139],{"class":34,"line":1130},33,[32,1132,1133],{"class":159},"            ch ",[32,1135,1000],{"class":151},[32,1137,1138],{"class":159}," i  ",[32,1140,1141],{"class":38},"\u002F\u002F blocks if no one receives — leak\n",[32,1143,1145],{"class":34,"line":1144},34,[32,1146,1033],{"class":159},[32,1148,1150],{"class":34,"line":1149},35,[32,1151,608],{"class":159},[32,1153,1155],{"class":34,"line":1154},36,[32,1156,1157],{"class":38},"    \u002F\u002F receiver never starts → goroutine stuck on first send\n",[32,1159,1161],{"class":34,"line":1160},37,[32,1162,235],{"class":159},[32,1164,1166],{"class":34,"line":1165},38,[32,1167,145],{"emptyLinePlaceholder":144},[32,1169,1171],{"class":34,"line":1170},39,[32,1172,1173],{"class":38},"\u002F\u002F ✅ FIX: buffered channel + context, or select with ctx.Done\n",[32,1175,1177,1179,1182,1184,1186,1188,1190,1192,1194,1196,1198],{"class":34,"line":1176},40,[32,1178,152],{"class":151},[32,1180,1181],{"class":155}," safeProducer",[32,1183,184],{"class":159},[32,1185,926],{"class":272},[32,1187,929],{"class":155},[32,1189,291],{"class":159},[32,1191,934],{"class":155},[32,1193,937],{"class":159},[32,1195,940],{"class":151},[32,1197,854],{"class":151},[32,1199,345],{"class":159},[32,1201,1203,1205,1207,1209,1211,1213,1215,1217,1220,1222],{"class":34,"line":1202},41,[32,1204,843],{"class":159},[32,1206,319],{"class":151},[32,1208,737],{"class":155},[32,1210,184],{"class":159},[32,1212,742],{"class":151},[32,1214,854],{"class":151},[32,1216,279],{"class":159},[32,1218,1219],{"class":199},"100",[32,1221,600],{"class":159},[32,1223,1224],{"class":38},"\u002F\u002F buffer absorbs bursts\n",[32,1226,1228,1230,1232],{"class":34,"line":1227},42,[32,1229,580],{"class":151},[32,1231,583],{"class":151},[32,1233,160],{"class":159},[32,1235,1237,1239,1241],{"class":34,"line":1236},43,[32,1238,672],{"class":151},[32,1240,979],{"class":155},[32,1242,982],{"class":159},[32,1244,1246,1248,1250,1252,1254,1257,1259],{"class":34,"line":1245},44,[32,1247,1102],{"class":151},[32,1249,1105],{"class":159},[32,1251,319],{"class":151},[32,1253,1110],{"class":199},[32,1255,1256],{"class":159},"; ; i",[32,1258,1125],{"class":151},[32,1260,345],{"class":159},[32,1262,1264,1267],{"class":34,"line":1263},45,[32,1265,1266],{"class":151},"            select",[32,1268,345],{"class":159},[32,1270,1272,1275,1277,1279],{"class":34,"line":1271},46,[32,1273,1274],{"class":151},"            case",[32,1276,997],{"class":159},[32,1278,1000],{"class":151},[32,1280,1281],{"class":159}," i:\n",[32,1283,1285,1287,1289,1291,1293],{"class":34,"line":1284},47,[32,1286,1274],{"class":151},[32,1288,877],{"class":151},[32,1290,1015],{"class":159},[32,1292,308],{"class":155},[32,1294,1006],{"class":159},[32,1296,1298,1301],{"class":34,"line":1297},48,[32,1299,1300],{"class":151},"                return",[32,1302,1303],{"class":38},"  \u002F\u002F exit when cancelled\n",[32,1305,1307],{"class":34,"line":1306},49,[32,1308,1309],{"class":159},"            }\n",[32,1311,1313],{"class":34,"line":1312},50,[32,1314,1033],{"class":159},[32,1316,1318],{"class":34,"line":1317},51,[32,1319,608],{"class":159},[32,1321,1323,1325],{"class":34,"line":1322},52,[32,1324,1042],{"class":151},[32,1326,1045],{"class":159},[32,1328,1330],{"class":34,"line":1329},53,[32,1331,235],{"class":159},[14,1333,1335,1336],{"id":1334},"detecting-leaks-with-pprof","Detecting Leaks with ",[29,1337,1338],{},"pprof",[19,1340,1341,1446,1450],{"language":21},[23,1342,1344],{"className":25,"code":1343,"language":21,"meta":27,"style":27},"\u002F\u002F import _ \"net\u002Fhttp\u002Fpprof\"\n\u002F\u002F go func() {\n\u002F\u002F     log.Println(http.ListenAndServe(\"localhost:6060\", nil))\n\u002F\u002F }()\n\n\u002F\u002F Then, in another terminal:\n\u002F\u002F go tool pprof http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine\n\u002F\u002F (pprof) top\n\u002F\u002F (pprof) traces\n\n\u002F\u002F This shows all live goroutines and their stack traces.\n\u002F\u002F Leaked goroutines appear as stuck on a channel send\u002Freceive.\n\u002F\u002F Look for goroutines that don't have a \u003C-ctx.Done() exit path.\n\n\u002F\u002F In tests, use go.uber.org\u002Fgoleak to detect leaks:\n\u002F\u002F\n\u002F\u002F import \"go.uber.org\u002Fgoleak\"\n\u002F\u002F func TestMain(m *testing.M) {\n\u002F\u002F     goleak.VerifyTestMain(m)  \u002F\u002F fails if goroutines leak after tests\n\u002F\u002F }\n",[29,1345,1346,1351,1356,1361,1366,1370,1375,1380,1385,1390,1394,1399,1404,1409,1413,1418,1423,1428,1433,1441],{"__ignoreMap":27},[32,1347,1348],{"class":34,"line":35},[32,1349,1350],{"class":38},"\u002F\u002F import _ \"net\u002Fhttp\u002Fpprof\"\n",[32,1352,1353],{"class":34,"line":42},[32,1354,1355],{"class":38},"\u002F\u002F go func() {\n",[32,1357,1358],{"class":34,"line":48},[32,1359,1360],{"class":38},"\u002F\u002F     log.Println(http.ListenAndServe(\"localhost:6060\", nil))\n",[32,1362,1363],{"class":34,"line":54},[32,1364,1365],{"class":38},"\u002F\u002F }()\n",[32,1367,1368],{"class":34,"line":60},[32,1369,145],{"emptyLinePlaceholder":144},[32,1371,1372],{"class":34,"line":66},[32,1373,1374],{"class":38},"\u002F\u002F Then, in another terminal:\n",[32,1376,1377],{"class":34,"line":72},[32,1378,1379],{"class":38},"\u002F\u002F go tool pprof http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine\n",[32,1381,1382],{"class":34,"line":77},[32,1383,1384],{"class":38},"\u002F\u002F (pprof) top\n",[32,1386,1387],{"class":34,"line":83},[32,1388,1389],{"class":38},"\u002F\u002F (pprof) traces\n",[32,1391,1392],{"class":34,"line":89},[32,1393,145],{"emptyLinePlaceholder":144},[32,1395,1396],{"class":34,"line":95},[32,1397,1398],{"class":38},"\u002F\u002F This shows all live goroutines and their stack traces.\n",[32,1400,1401],{"class":34,"line":101},[32,1402,1403],{"class":38},"\u002F\u002F Leaked goroutines appear as stuck on a channel send\u002Freceive.\n",[32,1405,1406],{"class":34,"line":106},[32,1407,1408],{"class":38},"\u002F\u002F Look for goroutines that don't have a \u003C-ctx.Done() exit path.\n",[32,1410,1411],{"class":34,"line":112},[32,1412,145],{"emptyLinePlaceholder":144},[32,1414,1415],{"class":34,"line":118},[32,1416,1417],{"class":38},"\u002F\u002F In tests, use go.uber.org\u002Fgoleak to detect leaks:\n",[32,1419,1420],{"class":34,"line":123},[32,1421,1422],{"class":38},"\u002F\u002F\n",[32,1424,1425],{"class":34,"line":129},[32,1426,1427],{"class":38},"\u002F\u002F import \"go.uber.org\u002Fgoleak\"\n",[32,1429,1430],{"class":34,"line":135},[32,1431,1432],{"class":38},"\u002F\u002F func TestMain(m *testing.M) {\n",[32,1434,1435,1438],{"class":34,"line":141},[32,1436,1437],{"class":38},"\u002F\u002F     goleak.VerifyTestMain(m)",[32,1439,1440],{"class":38},"  \u002F\u002F fails if goroutines leak after tests\n",[32,1442,1443],{"class":34,"line":148},[32,1444,1445],{"class":38},"\u002F\u002F }\n",[14,1447,1449],{"id":1448},"unbounded-fan-out-the-resource-exhaustion-trap","Unbounded Fan-Out — The Resource Exhaustion Trap",[19,1451,1452,1858,1862],{"language":21},[23,1453,1455],{"className":25,"code":1454,"language":21,"meta":27,"style":27},"\u002F\u002F ❌ ANTI-PATTERN: unbounded goroutine creation\nfunc fetchAllBad(urls []string) {\n    for _, url := range urls {\n        go fetch(url)  \u002F\u002F creates len(urls) goroutines at once!\n    }\n    \u002F\u002F With 1M URLs: 1M goroutines, 1M HTTP connections, 1M DNS lookups.\n    \u002F\u002F Exhausts memory, file descriptors, and overwhelms the server.\n}\n\n\u002F\u002F ✅ CORRECT: worker pool — bounded concurrency\nfunc fetchAllGood(urls []string, maxWorkers int) {\n    jobs := make(chan string, len(urls))\n    results := make(chan error, len(urls))\n    var wg sync.WaitGroup\n\n    \u002F\u002F Start a fixed number of workers:\n    for i := 0; i \u003C maxWorkers; i++ {\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            for url := range jobs {\n                results \u003C- fetch(url)\n            }\n        }()\n    }\n\n    \u002F\u002F Send all jobs:\n    for _, url := range urls {\n        jobs \u003C- url\n    }\n    close(jobs)\n\n    \u002F\u002F Wait for all workers to finish:\n    go func() {\n        wg.Wait()\n        close(results)\n    }()\n\n    \u002F\u002F Collect results:\n    for err := range results {\n        if err != nil {\n            log.Printf(\"fetch error: %v\", err)\n        }\n    }\n}\n",[29,1456,1457,1462,1480,1492,1505,1509,1514,1519,1523,1527,1532,1556,1579,1601,1613,1617,1622,1643,1655,1663,1674,1689,1700,1704,1709,1713,1717,1722,1734,1744,1748,1756,1760,1765,1773,1781,1788,1792,1796,1801,1814,1827,1846,1850,1854],{"__ignoreMap":27},[32,1458,1459],{"class":34,"line":35},[32,1460,1461],{"class":38},"\u002F\u002F ❌ ANTI-PATTERN: unbounded goroutine creation\n",[32,1463,1464,1466,1469,1471,1474,1476,1478],{"class":34,"line":42},[32,1465,152],{"class":151},[32,1467,1468],{"class":155}," fetchAllBad",[32,1470,184],{"class":159},[32,1472,1473],{"class":272},"urls",[32,1475,427],{"class":159},[32,1477,430],{"class":151},[32,1479,297],{"class":159},[32,1481,1482,1484,1486,1488,1490],{"class":34,"line":48},[32,1483,468],{"class":151},[32,1485,471],{"class":159},[32,1487,319],{"class":151},[32,1489,476],{"class":151},[32,1491,479],{"class":159},[32,1493,1494,1496,1499,1502],{"class":34,"line":54},[32,1495,500],{"class":151},[32,1497,1498],{"class":155}," fetch",[32,1500,1501],{"class":159},"(url)  ",[32,1503,1504],{"class":38},"\u002F\u002F creates len(urls) goroutines at once!\n",[32,1506,1507],{"class":34,"line":60},[32,1508,383],{"class":159},[32,1510,1511],{"class":34,"line":66},[32,1512,1513],{"class":38},"    \u002F\u002F With 1M URLs: 1M goroutines, 1M HTTP connections, 1M DNS lookups.\n",[32,1515,1516],{"class":34,"line":72},[32,1517,1518],{"class":38},"    \u002F\u002F Exhausts memory, file descriptors, and overwhelms the server.\n",[32,1520,1521],{"class":34,"line":77},[32,1522,235],{"class":159},[32,1524,1525],{"class":34,"line":83},[32,1526,145],{"emptyLinePlaceholder":144},[32,1528,1529],{"class":34,"line":89},[32,1530,1531],{"class":38},"\u002F\u002F ✅ CORRECT: worker pool — bounded concurrency\n",[32,1533,1534,1536,1539,1541,1543,1545,1547,1549,1552,1554],{"class":34,"line":95},[32,1535,152],{"class":151},[32,1537,1538],{"class":155}," fetchAllGood",[32,1540,184],{"class":159},[32,1542,1473],{"class":272},[32,1544,427],{"class":159},[32,1546,430],{"class":151},[32,1548,279],{"class":159},[32,1550,1551],{"class":272},"maxWorkers",[32,1553,854],{"class":151},[32,1555,297],{"class":159},[32,1557,1558,1561,1563,1565,1567,1569,1571,1573,1576],{"class":34,"line":101},[32,1559,1560],{"class":159},"    jobs ",[32,1562,319],{"class":151},[32,1564,737],{"class":155},[32,1566,184],{"class":159},[32,1568,742],{"class":151},[32,1570,276],{"class":151},[32,1572,279],{"class":159},[32,1574,1575],{"class":155},"len",[32,1577,1578],{"class":159},"(urls))\n",[32,1580,1581,1584,1586,1588,1590,1592,1595,1597,1599],{"class":34,"line":106},[32,1582,1583],{"class":159},"    results ",[32,1585,319],{"class":151},[32,1587,737],{"class":155},[32,1589,184],{"class":159},[32,1591,742],{"class":151},[32,1593,1594],{"class":151}," error",[32,1596,279],{"class":159},[32,1598,1575],{"class":155},[32,1600,1578],{"class":159},[32,1602,1603,1605,1607,1609,1611],{"class":34,"line":112},[32,1604,453],{"class":151},[32,1606,456],{"class":159},[32,1608,288],{"class":155},[32,1610,291],{"class":159},[32,1612,463],{"class":155},[32,1614,1615],{"class":34,"line":118},[32,1616,145],{"emptyLinePlaceholder":144},[32,1618,1619],{"class":34,"line":123},[32,1620,1621],{"class":38},"    \u002F\u002F Start a fixed number of workers:\n",[32,1623,1624,1626,1628,1630,1632,1634,1636,1639,1641],{"class":34,"line":129},[32,1625,468],{"class":151},[32,1627,1105],{"class":159},[32,1629,319],{"class":151},[32,1631,1110],{"class":199},[32,1633,1113],{"class":159},[32,1635,1116],{"class":151},[32,1637,1638],{"class":159}," maxWorkers; i",[32,1640,1125],{"class":151},[32,1642,345],{"class":159},[32,1644,1645,1647,1649,1651,1653],{"class":34,"line":135},[32,1646,484],{"class":159},[32,1648,487],{"class":155},[32,1650,184],{"class":159},[32,1652,492],{"class":199},[32,1654,495],{"class":159},[32,1656,1657,1659,1661],{"class":34,"line":141},[32,1658,500],{"class":151},[32,1660,583],{"class":151},[32,1662,160],{"class":159},[32,1664,1665,1668,1670,1672],{"class":34,"line":148},[32,1666,1667],{"class":151},"            defer",[32,1669,305],{"class":159},[32,1671,308],{"class":155},[32,1673,311],{"class":159},[32,1675,1676,1679,1682,1684,1686],{"class":34,"line":163},[32,1677,1678],{"class":151},"            for",[32,1680,1681],{"class":159}," url ",[32,1683,319],{"class":151},[32,1685,476],{"class":151},[32,1687,1688],{"class":159}," jobs {\n",[32,1690,1691,1694,1696,1698],{"class":34,"line":169},[32,1692,1693],{"class":159},"                results ",[32,1695,1000],{"class":151},[32,1697,1498],{"class":155},[32,1699,328],{"class":159},[32,1701,1702],{"class":34,"line":175},[32,1703,1309],{"class":159},[32,1705,1706],{"class":34,"line":209},[32,1707,1708],{"class":159},"        }()\n",[32,1710,1711],{"class":34,"line":214},[32,1712,383],{"class":159},[32,1714,1715],{"class":34,"line":220},[32,1716,145],{"emptyLinePlaceholder":144},[32,1718,1719],{"class":34,"line":226},[32,1720,1721],{"class":38},"    \u002F\u002F Send all jobs:\n",[32,1723,1724,1726,1728,1730,1732],{"class":34,"line":232},[32,1725,468],{"class":151},[32,1727,471],{"class":159},[32,1729,319],{"class":151},[32,1731,476],{"class":151},[32,1733,479],{"class":159},[32,1735,1736,1739,1741],{"class":34,"line":1061},[32,1737,1738],{"class":159},"        jobs ",[32,1740,1000],{"class":151},[32,1742,1743],{"class":159}," url\n",[32,1745,1746],{"class":34,"line":1071},[32,1747,383],{"class":159},[32,1749,1750,1753],{"class":34,"line":1090},[32,1751,1752],{"class":155},"    close",[32,1754,1755],{"class":159},"(jobs)\n",[32,1757,1758],{"class":34,"line":1099},[32,1759,145],{"emptyLinePlaceholder":144},[32,1761,1762],{"class":34,"line":1130},[32,1763,1764],{"class":38},"    \u002F\u002F Wait for all workers to finish:\n",[32,1766,1767,1769,1771],{"class":34,"line":1144},[32,1768,580],{"class":151},[32,1770,583],{"class":151},[32,1772,160],{"class":159},[32,1774,1775,1777,1779],{"class":34,"line":1149},[32,1776,484],{"class":159},[32,1778,526],{"class":155},[32,1780,311],{"class":159},[32,1782,1783,1785],{"class":34,"line":1154},[32,1784,774],{"class":155},[32,1786,1787],{"class":159},"(results)\n",[32,1789,1790],{"class":34,"line":1160},[32,1791,608],{"class":159},[32,1793,1794],{"class":34,"line":1165},[32,1795,145],{"emptyLinePlaceholder":144},[32,1797,1798],{"class":34,"line":1170},[32,1799,1800],{"class":38},"    \u002F\u002F Collect results:\n",[32,1802,1803,1805,1807,1809,1811],{"class":34,"line":1176},[32,1804,468],{"class":151},[32,1806,336],{"class":159},[32,1808,319],{"class":151},[32,1810,476],{"class":151},[32,1812,1813],{"class":159}," results {\n",[32,1815,1816,1819,1821,1823,1825],{"class":34,"line":1202},[32,1817,1818],{"class":151},"        if",[32,1820,336],{"class":159},[32,1822,339],{"class":151},[32,1824,342],{"class":199},[32,1826,345],{"class":159},[32,1828,1829,1832,1834,1836,1839,1841,1843],{"class":34,"line":1227},[32,1830,1831],{"class":159},"            log.",[32,1833,353],{"class":155},[32,1835,184],{"class":159},[32,1837,1838],{"class":187},"\"fetch error: ",[32,1840,367],{"class":199},[32,1842,370],{"class":187},[32,1844,1845],{"class":159},", err)\n",[32,1847,1848],{"class":34,"line":1236},[32,1849,1033],{"class":159},[32,1851,1852],{"class":34,"line":1245},[32,1853,383],{"class":159},[32,1855,1856],{"class":34,"line":1263},[32,1857,235],{"class":159},[14,1859,1861],{"id":1860},"the-closure-capture-trap-pre-122","The Closure Capture Trap (Pre-1.22)",[19,1863,1864,2117,2121],{"language":21},[23,1865,1867],{"className":25,"code":1866,"language":21,"meta":27,"style":27},"\u002F\u002F ❌ Pre-1.22: all goroutines capture the SAME i — print \"3 3 3\"\nfunc captureBad() {\n    for i := 0; i \u003C 3; i++ {\n        go func() {\n            fmt.Println(i)  \u002F\u002F captures i by reference — sees final value\n        }()\n    }\n    time.Sleep(time.Second)\n}\n\n\u002F\u002F ✅ Fix (portable): pass i as an argument — fresh copy per goroutine\nfunc captureGood() {\n    for i := 0; i \u003C 3; i++ {\n        go func(i int) {\n            fmt.Println(i)  \u002F\u002F i is a parameter — distinct per call\n        }(i)\n    }\n}\n\n\u002F\u002F ✅ Fix (portable): shadow i — new variable per iteration\nfunc captureShadow() {\n    for i := 0; i \u003C 3; i++ {\n        i := i  \u002F\u002F shadow — new i per iteration\n        go func() { fmt.Println(i) }()\n    }\n}\n\n\u002F\u002F Go 1.22+: the original code is safe — each iteration has its own i.\n\u002F\u002F The `go func(i int) { ... }(i)` pattern is still correct on 1.22+.\n",[29,1868,1869,1874,1883,1906,1914,1927,1931,1935,1946,1950,1954,1959,1968,1990,2005,2016,2021,2025,2029,2033,2038,2047,2069,2081,2095,2099,2103,2107,2112],{"__ignoreMap":27},[32,1870,1871],{"class":34,"line":35},[32,1872,1873],{"class":38},"\u002F\u002F ❌ Pre-1.22: all goroutines capture the SAME i — print \"3 3 3\"\n",[32,1875,1876,1878,1881],{"class":34,"line":42},[32,1877,152],{"class":151},[32,1879,1880],{"class":155}," captureBad",[32,1882,160],{"class":159},[32,1884,1885,1887,1889,1891,1893,1895,1897,1900,1902,1904],{"class":34,"line":48},[32,1886,468],{"class":151},[32,1888,1105],{"class":159},[32,1890,319],{"class":151},[32,1892,1110],{"class":199},[32,1894,1113],{"class":159},[32,1896,1116],{"class":151},[32,1898,1899],{"class":199}," 3",[32,1901,1122],{"class":159},[32,1903,1125],{"class":151},[32,1905,345],{"class":159},[32,1907,1908,1910,1912],{"class":34,"line":54},[32,1909,500],{"class":151},[32,1911,583],{"class":151},[32,1913,160],{"class":159},[32,1915,1916,1919,1921,1924],{"class":34,"line":60},[32,1917,1918],{"class":159},"            fmt.",[32,1920,181],{"class":155},[32,1922,1923],{"class":159},"(i)  ",[32,1925,1926],{"class":38},"\u002F\u002F captures i by reference — sees final value\n",[32,1928,1929],{"class":34,"line":66},[32,1930,1708],{"class":159},[32,1932,1933],{"class":34,"line":72},[32,1934,383],{"class":159},[32,1936,1937,1940,1943],{"class":34,"line":77},[32,1938,1939],{"class":159},"    time.",[32,1941,1942],{"class":155},"Sleep",[32,1944,1945],{"class":159},"(time.Second)\n",[32,1947,1948],{"class":34,"line":83},[32,1949,235],{"class":159},[32,1951,1952],{"class":34,"line":89},[32,1953,145],{"emptyLinePlaceholder":144},[32,1955,1956],{"class":34,"line":95},[32,1957,1958],{"class":38},"\u002F\u002F ✅ Fix (portable): pass i as an argument — fresh copy per goroutine\n",[32,1960,1961,1963,1966],{"class":34,"line":101},[32,1962,152],{"class":151},[32,1964,1965],{"class":155}," captureGood",[32,1967,160],{"class":159},[32,1969,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988],{"class":34,"line":106},[32,1971,468],{"class":151},[32,1973,1105],{"class":159},[32,1975,319],{"class":151},[32,1977,1110],{"class":199},[32,1979,1113],{"class":159},[32,1981,1116],{"class":151},[32,1983,1899],{"class":199},[32,1985,1122],{"class":159},[32,1987,1125],{"class":151},[32,1989,345],{"class":159},[32,1991,1992,1994,1996,1998,2001,2003],{"class":34,"line":112},[32,1993,500],{"class":151},[32,1995,583],{"class":151},[32,1997,184],{"class":159},[32,1999,2000],{"class":272},"i",[32,2002,854],{"class":151},[32,2004,297],{"class":159},[32,2006,2007,2009,2011,2013],{"class":34,"line":118},[32,2008,1918],{"class":159},[32,2010,181],{"class":155},[32,2012,1923],{"class":159},[32,2014,2015],{"class":38},"\u002F\u002F i is a parameter — distinct per call\n",[32,2017,2018],{"class":34,"line":123},[32,2019,2020],{"class":159},"        }(i)\n",[32,2022,2023],{"class":34,"line":129},[32,2024,383],{"class":159},[32,2026,2027],{"class":34,"line":135},[32,2028,235],{"class":159},[32,2030,2031],{"class":34,"line":141},[32,2032,145],{"emptyLinePlaceholder":144},[32,2034,2035],{"class":34,"line":148},[32,2036,2037],{"class":38},"\u002F\u002F ✅ Fix (portable): shadow i — new variable per iteration\n",[32,2039,2040,2042,2045],{"class":34,"line":163},[32,2041,152],{"class":151},[32,2043,2044],{"class":155}," captureShadow",[32,2046,160],{"class":159},[32,2048,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067],{"class":34,"line":169},[32,2050,468],{"class":151},[32,2052,1105],{"class":159},[32,2054,319],{"class":151},[32,2056,1110],{"class":199},[32,2058,1113],{"class":159},[32,2060,1116],{"class":151},[32,2062,1899],{"class":199},[32,2064,1122],{"class":159},[32,2066,1125],{"class":151},[32,2068,345],{"class":159},[32,2070,2071,2074,2076,2078],{"class":34,"line":175},[32,2072,2073],{"class":159},"        i ",[32,2075,319],{"class":151},[32,2077,1138],{"class":159},[32,2079,2080],{"class":38},"\u002F\u002F shadow — new i per iteration\n",[32,2082,2083,2085,2087,2090,2092],{"class":34,"line":209},[32,2084,500],{"class":151},[32,2086,583],{"class":151},[32,2088,2089],{"class":159},"() { fmt.",[32,2091,181],{"class":155},[32,2093,2094],{"class":159},"(i) }()\n",[32,2096,2097],{"class":34,"line":214},[32,2098,383],{"class":159},[32,2100,2101],{"class":34,"line":220},[32,2102,235],{"class":159},[32,2104,2105],{"class":34,"line":226},[32,2106,145],{"emptyLinePlaceholder":144},[32,2108,2109],{"class":34,"line":232},[32,2110,2111],{"class":38},"\u002F\u002F Go 1.22+: the original code is safe — each iteration has its own i.\n",[32,2113,2114],{"class":34,"line":1061},[32,2115,2116],{"class":38},"\u002F\u002F The `go func(i int) { ... }(i)` pattern is still correct on 1.22+.\n",[14,2118,2120],{"id":2119},"panic-in-goroutines-program-crash","Panic in Goroutines — Program Crash",[19,2122,2123,2342,2346],{"language":21},[23,2124,2126],{"className":25,"code":2125,"language":21,"meta":27,"style":27},"\u002F\u002F ❌ An unrecovered panic in a goroutine CRASHES THE ENTIRE PROGRAM.\n\u002F\u002F   go func() {\n\u002F\u002F       panic(\"boom\")  \u002F\u002F terminates the whole process, not just this goroutine\n\u002F\u002F   }()\n\n\u002F\u002F ✅ Always recover in goroutines that handle external input:\nfunc safeGo(fn func()) {\n    go func() {\n        defer func() {\n            if r := recover(); r != nil {\n                log.Printf(\"goroutine panic: %v\\n%s\", r, debug.Stack())\n            }\n        }()\n        fn()\n    }()\n}\n\n\u002F\u002F Usage in an HTTP server — one bad request shouldn't crash the server:\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    safeGo(func() {\n        processBackgroundJob(r.Context())\n    })\n}\n",[29,2127,2128,2133,2138,2146,2151,2155,2160,2177,2185,2193,2215,2241,2245,2249,2256,2260,2264,2268,2273,2310,2321,2333,2338],{"__ignoreMap":27},[32,2129,2130],{"class":34,"line":35},[32,2131,2132],{"class":38},"\u002F\u002F ❌ An unrecovered panic in a goroutine CRASHES THE ENTIRE PROGRAM.\n",[32,2134,2135],{"class":34,"line":42},[32,2136,2137],{"class":38},"\u002F\u002F   go func() {\n",[32,2139,2140,2143],{"class":34,"line":48},[32,2141,2142],{"class":38},"\u002F\u002F       panic(\"boom\")",[32,2144,2145],{"class":38},"  \u002F\u002F terminates the whole process, not just this goroutine\n",[32,2147,2148],{"class":34,"line":54},[32,2149,2150],{"class":38},"\u002F\u002F   }()\n",[32,2152,2153],{"class":34,"line":60},[32,2154,145],{"emptyLinePlaceholder":144},[32,2156,2157],{"class":34,"line":66},[32,2158,2159],{"class":38},"\u002F\u002F ✅ Always recover in goroutines that handle external input:\n",[32,2161,2162,2164,2167,2169,2172,2174],{"class":34,"line":72},[32,2163,152],{"class":151},[32,2165,2166],{"class":155}," safeGo",[32,2168,184],{"class":159},[32,2170,2171],{"class":272},"fn",[32,2173,583],{"class":151},[32,2175,2176],{"class":159},"()) {\n",[32,2178,2179,2181,2183],{"class":34,"line":77},[32,2180,580],{"class":151},[32,2182,583],{"class":151},[32,2184,160],{"class":159},[32,2186,2187,2189,2191],{"class":34,"line":83},[32,2188,672],{"class":151},[32,2190,583],{"class":151},[32,2192,160],{"class":159},[32,2194,2195,2198,2201,2203,2206,2209,2211,2213],{"class":34,"line":89},[32,2196,2197],{"class":151},"            if",[32,2199,2200],{"class":159}," r ",[32,2202,319],{"class":151},[32,2204,2205],{"class":155}," recover",[32,2207,2208],{"class":159},"(); r ",[32,2210,339],{"class":151},[32,2212,342],{"class":199},[32,2214,345],{"class":159},[32,2216,2217,2220,2222,2224,2227,2230,2232,2235,2238],{"class":34,"line":95},[32,2218,2219],{"class":159},"                log.",[32,2221,353],{"class":155},[32,2223,184],{"class":159},[32,2225,2226],{"class":187},"\"goroutine panic: ",[32,2228,2229],{"class":199},"%v\\n%s",[32,2231,370],{"class":187},[32,2233,2234],{"class":159},", r, debug.",[32,2236,2237],{"class":155},"Stack",[32,2239,2240],{"class":159},"())\n",[32,2242,2243],{"class":34,"line":101},[32,2244,1309],{"class":159},[32,2246,2247],{"class":34,"line":106},[32,2248,1708],{"class":159},[32,2250,2251,2254],{"class":34,"line":112},[32,2252,2253],{"class":155},"        fn",[32,2255,311],{"class":159},[32,2257,2258],{"class":34,"line":118},[32,2259,608],{"class":159},[32,2261,2262],{"class":34,"line":123},[32,2263,235],{"class":159},[32,2265,2266],{"class":34,"line":129},[32,2267,145],{"emptyLinePlaceholder":144},[32,2269,2270],{"class":34,"line":135},[32,2271,2272],{"class":38},"\u002F\u002F Usage in an HTTP server — one bad request shouldn't crash the server:\n",[32,2274,2275,2277,2280,2282,2285,2288,2290,2293,2295,2298,2300,2303,2305,2308],{"class":34,"line":141},[32,2276,152],{"class":151},[32,2278,2279],{"class":155}," handler",[32,2281,184],{"class":159},[32,2283,2284],{"class":272},"w",[32,2286,2287],{"class":155}," http",[32,2289,291],{"class":159},[32,2291,2292],{"class":155},"ResponseWriter",[32,2294,279],{"class":159},[32,2296,2297],{"class":272},"r",[32,2299,285],{"class":151},[32,2301,2302],{"class":155},"http",[32,2304,291],{"class":159},[32,2306,2307],{"class":155},"Request",[32,2309,297],{"class":159},[32,2311,2312,2315,2317,2319],{"class":34,"line":148},[32,2313,2314],{"class":155},"    safeGo",[32,2316,184],{"class":159},[32,2318,152],{"class":151},[32,2320,160],{"class":159},[32,2322,2323,2326,2329,2331],{"class":34,"line":163},[32,2324,2325],{"class":155},"        processBackgroundJob",[32,2327,2328],{"class":159},"(r.",[32,2330,934],{"class":155},[32,2332,2240],{"class":159},[32,2334,2335],{"class":34,"line":169},[32,2336,2337],{"class":159},"    })\n",[32,2339,2340],{"class":34,"line":175},[32,2341,235],{"class":159},[14,2343,2345],{"id":2344},"gomaxprocs-when-to-change-it","GOMAXPROCS — When to Change It",[19,2347,2348,2511,2515,2606,2610,2713,2717],{"language":21},[23,2349,2351],{"className":25,"code":2350,"language":21,"meta":27,"style":27},"func gomaxprocs() {\n    \u002F\u002F Default = NumCPU. Don't change it unless you have a specific reason.\n\n    \u002F\u002F Reasons to REDUCE GOMAXPROCS:\n    \u002F\u002F   - In containers with CPU limits, set GOMAXPROCS = the limit\n    \u002F\u002F     (Go reads the host's CPU count, not the cgroup limit — this\n    \u002F\u002F     over-allocates threads. Use automaxprocs or set it manually.)\n    \u002F\u002F   - In a shared environment where you want to leave cores for other\n    \u002F\u002F     processes.\n\n    \u002F\u002F Reasons to INCREASE GOMAXPROCS (rare):\n    \u002F\u002F   - I\u002FO-bound workload with many goroutines blocking on syscalls.\n    \u002F\u002F     More Ps allow more syscalls to run concurrently. But each P\n    \u002F\u002F     can spawn an M (OS thread), so this increases thread count.\n\n    \u002F\u002F ⚠️ In Kubernetes\u002FDocker with CPU limits:\n    \u002F\u002F   Go 1.22+ respects cgroup CPU limits automatically (GOMAXPROCS\n    \u002F\u002F   = the cgroup quota). Pre-1.22, use go.uber.org\u002Fautomaxprocs or\n    \u002F\u002F   set GOMAXPROCS explicitly.\n    runtime.GOMAXPROCS(4)  \u002F\u002F cap at 4 Ps\n\n    \u002F\u002F Query without changing:\n    n := runtime.GOMAXPROCS(0)\n    fmt.Println(\"Ps:\", n)\n}\n",[29,2352,2353,2362,2367,2371,2376,2381,2386,2391,2396,2401,2405,2410,2415,2420,2425,2429,2434,2439,2444,2449,2466,2470,2475,2493,2507],{"__ignoreMap":27},[32,2354,2355,2357,2360],{"class":34,"line":35},[32,2356,152],{"class":151},[32,2358,2359],{"class":155}," gomaxprocs",[32,2361,160],{"class":159},[32,2363,2364],{"class":34,"line":42},[32,2365,2366],{"class":38},"    \u002F\u002F Default = NumCPU. Don't change it unless you have a specific reason.\n",[32,2368,2369],{"class":34,"line":48},[32,2370,145],{"emptyLinePlaceholder":144},[32,2372,2373],{"class":34,"line":54},[32,2374,2375],{"class":38},"    \u002F\u002F Reasons to REDUCE GOMAXPROCS:\n",[32,2377,2378],{"class":34,"line":60},[32,2379,2380],{"class":38},"    \u002F\u002F   - In containers with CPU limits, set GOMAXPROCS = the limit\n",[32,2382,2383],{"class":34,"line":66},[32,2384,2385],{"class":38},"    \u002F\u002F     (Go reads the host's CPU count, not the cgroup limit — this\n",[32,2387,2388],{"class":34,"line":72},[32,2389,2390],{"class":38},"    \u002F\u002F     over-allocates threads. Use automaxprocs or set it manually.)\n",[32,2392,2393],{"class":34,"line":77},[32,2394,2395],{"class":38},"    \u002F\u002F   - In a shared environment where you want to leave cores for other\n",[32,2397,2398],{"class":34,"line":83},[32,2399,2400],{"class":38},"    \u002F\u002F     processes.\n",[32,2402,2403],{"class":34,"line":89},[32,2404,145],{"emptyLinePlaceholder":144},[32,2406,2407],{"class":34,"line":95},[32,2408,2409],{"class":38},"    \u002F\u002F Reasons to INCREASE GOMAXPROCS (rare):\n",[32,2411,2412],{"class":34,"line":101},[32,2413,2414],{"class":38},"    \u002F\u002F   - I\u002FO-bound workload with many goroutines blocking on syscalls.\n",[32,2416,2417],{"class":34,"line":106},[32,2418,2419],{"class":38},"    \u002F\u002F     More Ps allow more syscalls to run concurrently. But each P\n",[32,2421,2422],{"class":34,"line":112},[32,2423,2424],{"class":38},"    \u002F\u002F     can spawn an M (OS thread), so this increases thread count.\n",[32,2426,2427],{"class":34,"line":118},[32,2428,145],{"emptyLinePlaceholder":144},[32,2430,2431],{"class":34,"line":123},[32,2432,2433],{"class":38},"    \u002F\u002F ⚠️ In Kubernetes\u002FDocker with CPU limits:\n",[32,2435,2436],{"class":34,"line":129},[32,2437,2438],{"class":38},"    \u002F\u002F   Go 1.22+ respects cgroup CPU limits automatically (GOMAXPROCS\n",[32,2440,2441],{"class":34,"line":135},[32,2442,2443],{"class":38},"    \u002F\u002F   = the cgroup quota). Pre-1.22, use go.uber.org\u002Fautomaxprocs or\n",[32,2445,2446],{"class":34,"line":141},[32,2447,2448],{"class":38},"    \u002F\u002F   set GOMAXPROCS explicitly.\n",[32,2450,2451,2454,2456,2458,2461,2463],{"class":34,"line":148},[32,2452,2453],{"class":159},"    runtime.",[32,2455,194],{"class":155},[32,2457,184],{"class":159},[32,2459,2460],{"class":199},"4",[32,2462,600],{"class":159},[32,2464,2465],{"class":38},"\u002F\u002F cap at 4 Ps\n",[32,2467,2468],{"class":34,"line":163},[32,2469,145],{"emptyLinePlaceholder":144},[32,2471,2472],{"class":34,"line":169},[32,2473,2474],{"class":38},"    \u002F\u002F Query without changing:\n",[32,2476,2477,2480,2482,2485,2487,2489,2491],{"class":34,"line":175},[32,2478,2479],{"class":159},"    n ",[32,2481,319],{"class":151},[32,2483,2484],{"class":159}," runtime.",[32,2486,194],{"class":155},[32,2488,184],{"class":159},[32,2490,200],{"class":199},[32,2492,495],{"class":159},[32,2494,2495,2497,2499,2501,2504],{"class":34,"line":209},[32,2496,178],{"class":159},[32,2498,181],{"class":155},[32,2500,184],{"class":159},[32,2502,2503],{"class":187},"\"Ps:\"",[32,2505,2506],{"class":159},", n)\n",[32,2508,2509],{"class":34,"line":214},[32,2510,235],{"class":159},[14,2512,2514],{"id":2513},"tips-tricks","💡 Tips & Tricks",[2516,2517,2518,2538,2568,2576,2588,2598],"ul",{},[2519,2520,2521,2525,2526,2529,2530,2533,2534,2537],"li",{},[2522,2523,2524],"strong",{},"Idiom",": always pass ",[29,2527,2528],{},"context.Context"," to goroutines and ",[29,2531,2532],{},"select"," on ",[29,2535,2536],{},"\u003C-ctx.Done()"," — this gives every goroutine an exit path, preventing leaks. A goroutine that can block forever with no exit is a leak waiting to happen.",[2519,2539,2540,2542,2543,2546,2547,2550,2551,279,2554,2557,2558,2561,2562,2564,2565,2567],{},[2522,2541,2524],{},": use ",[29,2544,2545],{},"sync.WaitGroup"," to wait for a known set of goroutines — ",[29,2548,2549],{},"wg.Add(1)"," before ",[29,2552,2553],{},"go f()",[29,2555,2556],{},"defer wg.Done()"," inside, ",[29,2559,2560],{},"wg.Wait()"," to block. Never ",[29,2563,487],{}," inside the goroutine (race with ",[29,2566,526],{},").",[2519,2569,2570,2572,2573,2575],{},[2522,2571,2524],{},": use a worker pool for bounded concurrency — unbounded ",[29,2574,2553],{}," in a loop can create millions of goroutines, exhausting memory and overwhelming downstream resources (DB connections, API rate limits).",[2519,2577,2578,364,2581,2584,2585,2587],{},[2522,2579,2580],{},"Debug",[29,2582,2583],{},"go tool pprof http:\u002F\u002Flocalhost:6060\u002Fdebug\u002Fpprof\u002Fgoroutine"," shows all live goroutines and their stack traces — leaked goroutines appear stuck on a channel send\u002Freceive with no ",[29,2586,2536],{}," path.",[2519,2589,2590,2593,2594,2597],{},[2522,2591,2592],{},"Safety",": always ",[29,2595,2596],{},"recover"," in goroutines that handle external input — a single bad request causing a panic shouldn't crash the server. Log the panic + stack trace for debugging.",[2519,2599,2600,364,2602,2605],{},[2522,2601,2580],{},[29,2603,2604],{},"go.uber.org\u002Fgoleak.VerifyTestMain(m)"," in tests — fails if goroutines leak after the test suite. Catches leaks in CI before they hit production.",[14,2607,2609],{"id":2608},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2516,2611,2612,2624,2641,2653,2666,2672,2678,2690,2698,2704],{},[2519,2613,2614,364,2617,2620,2621,2623],{},[2522,2615,2616],{},"Main exiting kills all goroutines",[29,2618,2619],{},"main"," returning terminates the program immediately — goroutines are stopped without running deferred functions. Use ",[29,2622,294],{},"\u002Fchannels to wait.",[2519,2625,2626,2629,2630,2633,2634,2637,2638,2640],{},[2522,2627,2628],{},"Goroutine leak",": a goroutine blocked forever with no exit path consumes memory forever. Use ",[29,2631,2632],{},"context","\u002F",[29,2635,2636],{},"done"," channels and ",[29,2639,2532],{}," to ensure exit paths.",[2519,2642,2643,364,2646,2649,2650,2652],{},[2522,2644,2645],{},"Closure capturing loop variables (pre-1.22)",[29,2647,2648],{},"for i := 0; ... { go func() { use(i) }() }"," — all goroutines see the final ",[29,2651,2000],{},". Pass as argument or use Go 1.22+.",[2519,2654,2655,2658,2659,2662,2663,2665],{},[2522,2656,2657],{},"No goroutine ID",": Go deliberately doesn't expose goroutine IDs (no ",[29,2660,2661],{},"goroutine.ID()",") to discourage goroutine-local state. Use ",[29,2664,2632],{}," for request-scoped values.",[2519,2667,2668,2671],{},[2522,2669,2670],{},"Panic in a goroutine crashes the program",": unrecovered panics terminate the whole process. Recover in goroutines that handle external input.",[2519,2673,2674,2677],{},[2522,2675,2676],{},"Goroutines aren't free",": ~2KB stack minimum + scheduler overhead. Millions of goroutines doing trivial work waste resources — use a worker pool.",[2519,2679,2680,2685,2686,2689],{},[2522,2681,2682,2684],{},[29,2683,194],{}," default = CPU count",": usually correct. In containers with CPU limits (pre-1.22), set it explicitly or use ",[29,2687,2688],{},"automaxprocs"," — Go reads the host's CPU count, not the cgroup limit.",[2519,2691,2692,2697],{},[2522,2693,2694],{},[29,2695,2696],{},"runtime.LockOSThread",": binds a goroutine to an OS thread — needed for some C libraries (CGO) or runtime-specific APIs. Rare; don't use without a specific reason.",[2519,2699,2700,2703],{},[2522,2701,2702],{},"Goroutine scheduling is preemptive (Go 1.14+)",": a goroutine can be preempted even without blocking (async preemption). Long CPU loops don't starve other goroutines.",[2519,2705,2706,364,2709,2712],{},[2522,2707,2708],{},"Starting goroutines in a loop without bounds",[29,2710,2711],{},"for _, item := range huge { go process(item) }"," creates len(huge) goroutines at once — exhausts memory. Use a worker pool.",[14,2714,2716],{"id":2715},"quick-quiz","🧠 Quick Quiz",[19,2718,2719,2813],{"language":21},[23,2720,2722],{"className":25,"code":2721,"language":21,"meta":27,"style":27},"func main() {\n    for i := 0; i \u003C 3; i++ {\n        go func() {\n            time.Sleep(100 * time.Millisecond)\n            fmt.Print(i, \" \")\n        }()\n    }\n    time.Sleep(time.Second)\n}\n",[29,2723,2724,2732,2754,2762,2778,2793,2797,2801,2809],{"__ignoreMap":27},[32,2725,2726,2728,2730],{"class":34,"line":35},[32,2727,152],{"class":151},[32,2729,415],{"class":155},[32,2731,160],{"class":159},[32,2733,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752],{"class":34,"line":42},[32,2735,468],{"class":151},[32,2737,1105],{"class":159},[32,2739,319],{"class":151},[32,2741,1110],{"class":199},[32,2743,1113],{"class":159},[32,2745,1116],{"class":151},[32,2747,1899],{"class":199},[32,2749,1122],{"class":159},[32,2751,1125],{"class":151},[32,2753,345],{"class":159},[32,2755,2756,2758,2760],{"class":34,"line":48},[32,2757,500],{"class":151},[32,2759,583],{"class":151},[32,2761,160],{"class":159},[32,2763,2764,2767,2769,2771,2773,2775],{"class":34,"line":54},[32,2765,2766],{"class":159},"            time.",[32,2768,1942],{"class":155},[32,2770,184],{"class":159},[32,2772,1219],{"class":199},[32,2774,285],{"class":151},[32,2776,2777],{"class":159}," time.Millisecond)\n",[32,2779,2780,2782,2785,2788,2791],{"class":34,"line":60},[32,2781,1918],{"class":159},[32,2783,2784],{"class":155},"Print",[32,2786,2787],{"class":159},"(i, ",[32,2789,2790],{"class":187},"\" \"",[32,2792,495],{"class":159},[32,2794,2795],{"class":34,"line":66},[32,2796,1708],{"class":159},[32,2798,2799],{"class":34,"line":72},[32,2800,383],{"class":159},[32,2802,2803,2805,2807],{"class":34,"line":77},[32,2804,1939],{"class":159},[32,2806,1942],{"class":155},[32,2808,1945],{"class":159},[32,2810,2811],{"class":34,"line":83},[32,2812,235],{"class":159},[2814,2815,2816],"p",{},"On Go 1.22+, what's printed? (Order may vary.)",[2818,2819,2820,2824,2827,2835,2838,2844,2854],"details",{},[2821,2822,2823],"summary",{},"Answer",[2814,2825,2826],{},"Go 1.22+:",[23,2828,2833],{"className":2829,"code":2831,"language":2832},[2830],"language-text","0 1 2\n","text",[29,2834,2831],{"__ignoreMap":27},[2814,2836,2837],{},"(order may vary because goroutines run concurrently)",[2814,2839,2840,2841,2843],{},"Go 1.22 makes each iteration's loop variable distinct. Each goroutine captures its own ",[29,2842,2000],{},", so they print 0, 1, 2 (in some order).",[2814,2845,2846,2847,2850,2851,2853],{},"On pre-1.22, the output would be ",[29,2848,2849],{},"3 3 3"," — all goroutines capture the same ",[29,2852,2000],{},", which is 3 after the loop ends.",[2814,2855,2856,2857,2860,2861,2863],{},"The fix for pre-1.22 (portable to all versions): ",[29,2858,2859],{},"go func(i int) { ... }(i)"," — pass ",[29,2862,2000],{}," as an argument, creating a fresh copy per goroutine.",[14,2865,2867],{"id":2866},"whats-next","📚 What's Next",[2814,2869,2870,2871,2876],{},"→ ",[2872,2873,2875],"a",{"href":2874},"\u002Fgo\u002F17-channels","17 — Channels"," — unbuffered vs buffered semantics, close rules, directional channels, and nil channel patterns in select.",[2878,2879,2880],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":27,"searchDepth":42,"depth":42,"links":2882},[2883,2884,2885],{"id":16,"depth":42,"text":17},{"id":238,"depth":42,"text":239},{"id":2866,"depth":42,"text":2867},"Goroutine scheduling model (G-M-P), stack growth, GOMAXPROCS, leak prevention, and the preemptive scheduler — the concurrency engine.","md",{},"\u002Fgo\u002F16-goroutines",{"title":5,"description":2886},"go\u002F16-goroutines","HNxFRUkuGiDVSCJ4cqL3yZ2Vg2PP9BdoRMQEYBS8dNM",1789924648563]