[{"data":1,"prerenderedAt":5747},["ShallowReactive",2],{"content:\u002F03-concurrency\u002F01-goroutines-channels":3},{"title":4,"description":5,"path":6,"body":7},"Горутины и каналы","Конкурентность — одна из главных причин, по которой Go стал популярен в backend-разработке. Модель построена на двух примитивах: горутинах как единице исполнения и каналах как способе коммуникации между ними. Понять их в отрыве друг от друга можно, но бессмысленно — они проектировались вместе и работают вместе.","\u002F03-concurrency\u002F01-goroutines-channels",{"type":8,"value":9,"toc":5717},"minimark",[10,15,18,24,27,32,40,237,247,252,255,321,329,404,408,415,521,525,528,617,674,681,787,794,846,848,852,858,908,912,919,1081,1084,1088,1091,1213,1216,1220,1227,1240,1411,1417,1421,1424,1606,1608,1612,1622,1768,1772,1778,1859,1863,1866,2124,2133,2137,2140,2568,2570,2574,2577,2581,2649,2655,2659,2665,2798,2813,2815,2819,2822,2871,2993,3000,3002,3006,3010,3163,3167,3205,3209,3295,3297,3301,3310,3329,3337,3349,3360,3368,3391,3415,3441,3453,3469,3473,3475,3480,3486,3492,3502,3507,3515,3520,3809,3811,3816,3821,3826,3848,3852,3858,3862,4232,4234,4239,4244,4249,4269,4273,4279,4288,4292,5018,5020,5024,5065,5296,5422,5713],[11,12,14],"h1",{"id":13},"горутины-и-каналы-в-go","Горутины и каналы в Go",[16,17,5],"p",{},[19,20,21],"blockquote",{},[16,22,23],{},"\"Do not communicate by sharing memory; instead, share memory by communicating\" — главный принцип конкурентности в Go.",[25,26],"hr",{},[28,29,31],"h2",{"id":30},"горутины","Горутины",[16,33,34,35,39],{},"Горутина — это легковесный поток исполнения, управляемый рантаймом Go, а не операционной системой. Запускается ключевым словом ",[36,37,38],"code",{},"go",":",[41,42,46],"pre",{"className":43,"code":44,"language":38,"meta":45,"style":45},"language-go shiki shiki-themes github-dark","package main\n\nimport (\n    \"fmt\"\n    \"time\"\n)\n\nfunc main() {\n    go sayHello() \u002F\u002F запуск горутины — не блокирует\n    go func() {   \u002F\u002F анонимная функция\n        fmt.Println(\"anonymous goroutine\")\n    }()\n\n    time.Sleep(time.Second) \u002F\u002F даём горутинам время выполниться\n}\n\nfunc sayHello() {\n    fmt.Println(\"hello from goroutine\")\n}\n","",[36,47,48,61,68,78,91,101,107,112,124,140,154,171,177,182,197,203,208,217,232],{"__ignoreMap":45},[49,50,53,57],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"snl16","package",[49,58,60],{"class":59},"svObZ"," main\n",[49,62,64],{"class":51,"line":63},2,[49,65,67],{"emptyLinePlaceholder":66},true,"\n",[49,69,71,74],{"class":51,"line":70},3,[49,72,73],{"class":55},"import",[49,75,77],{"class":76},"s95oV"," (\n",[49,79,81,85,88],{"class":51,"line":80},4,[49,82,84],{"class":83},"sU2Wk","    \"",[49,86,87],{"class":59},"fmt",[49,89,90],{"class":83},"\"\n",[49,92,94,96,99],{"class":51,"line":93},5,[49,95,84],{"class":83},[49,97,98],{"class":59},"time",[49,100,90],{"class":83},[49,102,104],{"class":51,"line":103},6,[49,105,106],{"class":76},")\n",[49,108,110],{"class":51,"line":109},7,[49,111,67],{"emptyLinePlaceholder":66},[49,113,115,118,121],{"class":51,"line":114},8,[49,116,117],{"class":55},"func",[49,119,120],{"class":59}," main",[49,122,123],{"class":76},"() {\n",[49,125,127,130,133,136],{"class":51,"line":126},9,[49,128,129],{"class":55},"    go",[49,131,132],{"class":59}," sayHello",[49,134,135],{"class":76},"() ",[49,137,139],{"class":138},"sAwPA","\u002F\u002F запуск горутины — не блокирует\n",[49,141,143,145,148,151],{"class":51,"line":142},10,[49,144,129],{"class":55},[49,146,147],{"class":55}," func",[49,149,150],{"class":76},"() {   ",[49,152,153],{"class":138},"\u002F\u002F анонимная функция\n",[49,155,157,160,163,166,169],{"class":51,"line":156},11,[49,158,159],{"class":76},"        fmt.",[49,161,162],{"class":59},"Println",[49,164,165],{"class":76},"(",[49,167,168],{"class":83},"\"anonymous goroutine\"",[49,170,106],{"class":76},[49,172,174],{"class":51,"line":173},12,[49,175,176],{"class":76},"    }()\n",[49,178,180],{"class":51,"line":179},13,[49,181,67],{"emptyLinePlaceholder":66},[49,183,185,188,191,194],{"class":51,"line":184},14,[49,186,187],{"class":76},"    time.",[49,189,190],{"class":59},"Sleep",[49,192,193],{"class":76},"(time.Second) ",[49,195,196],{"class":138},"\u002F\u002F даём горутинам время выполниться\n",[49,198,200],{"class":51,"line":199},15,[49,201,202],{"class":76},"}\n",[49,204,206],{"class":51,"line":205},16,[49,207,67],{"emptyLinePlaceholder":66},[49,209,211,213,215],{"class":51,"line":210},17,[49,212,117],{"class":55},[49,214,132],{"class":59},[49,216,123],{"class":76},[49,218,220,223,225,227,230],{"class":51,"line":219},18,[49,221,222],{"class":76},"    fmt.",[49,224,162],{"class":59},[49,226,165],{"class":76},[49,228,229],{"class":83},"\"hello from goroutine\"",[49,231,106],{"class":76},[49,233,235],{"class":51,"line":234},19,[49,236,202],{"class":76},[16,238,239,242,243,246],{},[36,240,241],{},"time.Sleep"," здесь — плохой способ ждать горутины. Правильный — каналы или ",[36,244,245],{},"sync.WaitGroup",". К этому вернёмся чуть ниже.",[248,249,251],"h3",{"id":250},"почему-горутины-дёшевы","Почему горутины дёшевы",[16,253,254],{},"Разница между горутиной и OS-потоком принципиальная:",[256,257,258,273],"table",{},[259,260,261],"thead",{},[262,263,264,268,271],"tr",{},[265,266,267],"th",{},"OS Thread",[265,269,270],{},"Горутина",[265,272],{},[274,275,276,288,299,310],"tbody",{},[262,277,278,282,285],{},[279,280,281],"td",{},"Начальный стек",[279,283,284],{},"1–8 МБ (фиксирован)",[279,286,287],{},"2–8 КБ (растёт динамически)",[262,289,290,293,296],{},[279,291,292],{},"Создание",[279,294,295],{},"~1–10 мкс (syscall)",[279,297,298],{},"~0.3 мкс (только рантайм)",[262,300,301,304,307],{},[279,302,303],{},"Переключение",[279,305,306],{},"~1–2 мкс (kernel)",[279,308,309],{},"~0.1 мкс (userspace)",[262,311,312,315,318],{},[279,313,314],{},"Типичное кол-во",[279,316,317],{},"тысячи",[279,319,320],{},"сотни тысяч",[16,322,323,324,328],{},"Стек горутины начинается с 2–8 КБ и ",[325,326,327],"strong",{},"растёт по необходимости"," — рантайм автоматически выделяет больший стек и переносит содержимое. Это позволяет иметь сотни тысяч горутин одновременно без исчерпания памяти.",[41,330,332],{"className":43,"code":331,"language":38,"meta":45,"style":45},"\u002F\u002F Это нормально в Go\nfor i := 0; i \u003C 100_000; i++ {\n    go func(n int) {\n        \u002F\u002F какая-то работа\n    }(i)\n}\n",[36,333,334,339,372,390,395,400],{"__ignoreMap":45},[49,335,336],{"class":51,"line":52},[49,337,338],{"class":138},"\u002F\u002F Это нормально в Go\n",[49,340,341,344,347,350,354,357,360,363,366,369],{"class":51,"line":63},[49,342,343],{"class":55},"for",[49,345,346],{"class":76}," i ",[49,348,349],{"class":55},":=",[49,351,353],{"class":352},"sDLfK"," 0",[49,355,356],{"class":76},"; i ",[49,358,359],{"class":55},"\u003C",[49,361,362],{"class":352}," 100_000",[49,364,365],{"class":76},"; i",[49,367,368],{"class":55},"++",[49,370,371],{"class":76}," {\n",[49,373,374,376,378,380,384,387],{"class":51,"line":70},[49,375,129],{"class":55},[49,377,147],{"class":55},[49,379,165],{"class":76},[49,381,383],{"class":382},"s9osk","n",[49,385,386],{"class":55}," int",[49,388,389],{"class":76},") {\n",[49,391,392],{"class":51,"line":80},[49,393,394],{"class":138},"        \u002F\u002F какая-то работа\n",[49,396,397],{"class":51,"line":93},[49,398,399],{"class":76},"    }(i)\n",[49,401,402],{"class":51,"line":103},[49,403,202],{"class":76},[248,405,407],{"id":406},"жизненный-цикл-горутины","Жизненный цикл горутины",[16,409,410,411,414],{},"Горутина живёт пока не вернётся из функции, которую она запускает. Если ",[36,412,413],{},"main"," завершается — все горутины убиваются немедленно, независимо от их состояния:",[41,416,419],{"className":43,"code":417,"language":38,"meta":418,"style":45},"package main\n\nimport (\n    \"fmt\"\n    \"time\"\n)\n\nfunc main() {\n    go func() {\n        time.Sleep(10 * time.Second)\n        fmt.Println(\"я никогда не выполнюсь\")\n    }()\n    \u002F\u002F main заканчивается — горутина выше умирает вместе с процессом\n}\n","static",[36,420,421,427,431,437,445,453,457,461,469,477,495,508,512,517],{"__ignoreMap":45},[49,422,423,425],{"class":51,"line":52},[49,424,56],{"class":55},[49,426,60],{"class":59},[49,428,429],{"class":51,"line":63},[49,430,67],{"emptyLinePlaceholder":66},[49,432,433,435],{"class":51,"line":70},[49,434,73],{"class":55},[49,436,77],{"class":76},[49,438,439,441,443],{"class":51,"line":80},[49,440,84],{"class":83},[49,442,87],{"class":59},[49,444,90],{"class":83},[49,446,447,449,451],{"class":51,"line":93},[49,448,84],{"class":83},[49,450,98],{"class":59},[49,452,90],{"class":83},[49,454,455],{"class":51,"line":103},[49,456,106],{"class":76},[49,458,459],{"class":51,"line":109},[49,460,67],{"emptyLinePlaceholder":66},[49,462,463,465,467],{"class":51,"line":114},[49,464,117],{"class":55},[49,466,120],{"class":59},[49,468,123],{"class":76},[49,470,471,473,475],{"class":51,"line":126},[49,472,129],{"class":55},[49,474,147],{"class":55},[49,476,123],{"class":76},[49,478,479,482,484,486,489,492],{"class":51,"line":142},[49,480,481],{"class":76},"        time.",[49,483,190],{"class":59},[49,485,165],{"class":76},[49,487,488],{"class":352},"10",[49,490,491],{"class":55}," *",[49,493,494],{"class":76}," time.Second)\n",[49,496,497,499,501,503,506],{"class":51,"line":156},[49,498,159],{"class":76},[49,500,162],{"class":59},[49,502,165],{"class":76},[49,504,505],{"class":83},"\"я никогда не выполнюсь\"",[49,507,106],{"class":76},[49,509,510],{"class":51,"line":173},[49,511,176],{"class":76},[49,513,514],{"class":51,"line":179},[49,515,516],{"class":138},"    \u002F\u002F main заканчивается — горутина выше умирает вместе с процессом\n",[49,518,519],{"class":51,"line":184},[49,520,202],{"class":76},[248,522,524],{"id":523},"утечки-горутин","Утечки горутин",[16,526,527],{},"Утечка горутины — это когда горутина запущена, но никогда не завершается. Она держит память, стек и все захваченные переменные:",[41,529,531],{"className":43,"code":530,"language":38,"meta":418,"style":45},"\u002F\u002F Классическая утечка: горутина заблокирована на чтении из канала\n\u002F\u002F который никто никогда не закроет и в который никто не пишет\nfunc leak() {\n    ch := make(chan int)\n    go func() {\n        val := \u003C-ch \u002F\u002F блокируется навсегда\n        fmt.Println(val)\n    }()\n    \u002F\u002F ch выходит из scope, но горутина жива\n}\n",[36,532,533,538,543,552,571,579,595,604,608,613],{"__ignoreMap":45},[49,534,535],{"class":51,"line":52},[49,536,537],{"class":138},"\u002F\u002F Классическая утечка: горутина заблокирована на чтении из канала\n",[49,539,540],{"class":51,"line":63},[49,541,542],{"class":138},"\u002F\u002F который никто никогда не закроет и в который никто не пишет\n",[49,544,545,547,550],{"class":51,"line":70},[49,546,117],{"class":55},[49,548,549],{"class":59}," leak",[49,551,123],{"class":76},[49,553,554,557,559,562,564,567,569],{"class":51,"line":80},[49,555,556],{"class":76},"    ch ",[49,558,349],{"class":55},[49,560,561],{"class":59}," make",[49,563,165],{"class":76},[49,565,566],{"class":55},"chan",[49,568,386],{"class":55},[49,570,106],{"class":76},[49,572,573,575,577],{"class":51,"line":93},[49,574,129],{"class":55},[49,576,147],{"class":55},[49,578,123],{"class":76},[49,580,581,584,586,589,592],{"class":51,"line":103},[49,582,583],{"class":76},"        val ",[49,585,349],{"class":55},[49,587,588],{"class":55}," \u003C-",[49,590,591],{"class":76},"ch ",[49,593,594],{"class":138},"\u002F\u002F блокируется навсегда\n",[49,596,597,599,601],{"class":51,"line":109},[49,598,159],{"class":76},[49,600,162],{"class":59},[49,602,603],{"class":76},"(val)\n",[49,605,606],{"class":51,"line":114},[49,607,176],{"class":76},[49,609,610],{"class":51,"line":126},[49,611,612],{"class":138},"    \u002F\u002F ch выходит из scope, но горутина жива\n",[49,614,615],{"class":51,"line":142},[49,616,202],{"class":76},[41,618,620],{"className":43,"code":619,"language":38,"meta":45,"style":45},"\u002F\u002F Утечка через бесконечный цикл без условия выхода\nfunc leak2() {\n    go func() {\n        for {\n            doWork() \u002F\u002F нет способа остановить эту горутину снаружи\n        }\n    }()\n}\n",[36,621,622,627,636,644,651,661,666,670],{"__ignoreMap":45},[49,623,624],{"class":51,"line":52},[49,625,626],{"class":138},"\u002F\u002F Утечка через бесконечный цикл без условия выхода\n",[49,628,629,631,634],{"class":51,"line":63},[49,630,117],{"class":55},[49,632,633],{"class":59}," leak2",[49,635,123],{"class":76},[49,637,638,640,642],{"class":51,"line":70},[49,639,129],{"class":55},[49,641,147],{"class":55},[49,643,123],{"class":76},[49,645,646,649],{"class":51,"line":80},[49,647,648],{"class":55},"        for",[49,650,371],{"class":76},[49,652,653,656,658],{"class":51,"line":93},[49,654,655],{"class":59},"            doWork",[49,657,135],{"class":76},[49,659,660],{"class":138},"\u002F\u002F нет способа остановить эту горутину снаружи\n",[49,662,663],{"class":51,"line":103},[49,664,665],{"class":76},"        }\n",[49,667,668],{"class":51,"line":109},[49,669,176],{"class":76},[49,671,672],{"class":51,"line":114},[49,673,202],{"class":76},[16,675,676,677,680],{},"Правильный способ — всегда давать горутине способ завершиться. Обычно это ",[36,678,679],{},"context.Context"," или канал-сигнал:",[41,682,684],{"className":43,"code":683,"language":38,"meta":45,"style":45},"func noLeak(ctx context.Context) {\n    go func() {\n        for {\n            select {\n            case \u003C-ctx.Done(): \u002F\u002F получили сигнал завершения\n                return\n            default:\n                doWork()\n            }\n        }\n    }()\n}\n",[36,685,686,709,717,723,730,749,754,762,770,775,779,783],{"__ignoreMap":45},[49,687,688,690,693,695,698,701,704,707],{"class":51,"line":52},[49,689,117],{"class":55},[49,691,692],{"class":59}," noLeak",[49,694,165],{"class":76},[49,696,697],{"class":382},"ctx",[49,699,700],{"class":59}," context",[49,702,703],{"class":76},".",[49,705,706],{"class":59},"Context",[49,708,389],{"class":76},[49,710,711,713,715],{"class":51,"line":63},[49,712,129],{"class":55},[49,714,147],{"class":55},[49,716,123],{"class":76},[49,718,719,721],{"class":51,"line":70},[49,720,648],{"class":55},[49,722,371],{"class":76},[49,724,725,728],{"class":51,"line":80},[49,726,727],{"class":55},"            select",[49,729,371],{"class":76},[49,731,732,735,737,740,743,746],{"class":51,"line":93},[49,733,734],{"class":55},"            case",[49,736,588],{"class":55},[49,738,739],{"class":76},"ctx.",[49,741,742],{"class":59},"Done",[49,744,745],{"class":76},"(): ",[49,747,748],{"class":138},"\u002F\u002F получили сигнал завершения\n",[49,750,751],{"class":51,"line":103},[49,752,753],{"class":55},"                return\n",[49,755,756,759],{"class":51,"line":109},[49,757,758],{"class":55},"            default",[49,760,761],{"class":76},":\n",[49,763,764,767],{"class":51,"line":114},[49,765,766],{"class":59},"                doWork",[49,768,769],{"class":76},"()\n",[49,771,772],{"class":51,"line":126},[49,773,774],{"class":76},"            }\n",[49,776,777],{"class":51,"line":142},[49,778,665],{"class":76},[49,780,781],{"class":51,"line":156},[49,782,176],{"class":76},[49,784,785],{"class":51,"line":173},[49,786,202],{"class":76},[16,788,789,790,793],{},"Обнаружить утечки помогает ",[36,791,792],{},"goleak"," от Uber — библиотека для тестов, которая проверяет что после теста не осталось лишних горутин:",[41,795,797],{"className":43,"code":796,"language":38,"meta":45,"style":45},"func TestMyFunc(t *testing.T) {\n    defer goleak.VerifyNone(t)\n    \u002F\u002F тест\n}\n",[36,798,799,823,837,842],{"__ignoreMap":45},[49,800,801,803,806,808,811,813,816,818,821],{"class":51,"line":52},[49,802,117],{"class":55},[49,804,805],{"class":59}," TestMyFunc",[49,807,165],{"class":76},[49,809,810],{"class":382},"t",[49,812,491],{"class":55},[49,814,815],{"class":59},"testing",[49,817,703],{"class":76},[49,819,820],{"class":59},"T",[49,822,389],{"class":76},[49,824,825,828,831,834],{"class":51,"line":63},[49,826,827],{"class":55},"    defer",[49,829,830],{"class":76}," goleak.",[49,832,833],{"class":59},"VerifyNone",[49,835,836],{"class":76},"(t)\n",[49,838,839],{"class":51,"line":70},[49,840,841],{"class":138},"    \u002F\u002F тест\n",[49,843,844],{"class":51,"line":80},[49,845,202],{"class":76},[25,847],{},[28,849,851],{"id":850},"каналы","Каналы",[16,853,854,855,39],{},"Канал — это типизированный, потокобезопасный способ передавать данные между горутинами. Создаётся через ",[36,856,857],{},"make",[41,859,861],{"className":43,"code":860,"language":38,"meta":45,"style":45},"ch := make(chan int)        \u002F\u002F небуферизованный\nch := make(chan int, 10)    \u002F\u002F буферизованный на 10 элементов\n",[36,862,863,883],{"__ignoreMap":45},[49,864,865,867,869,871,873,875,877,880],{"class":51,"line":52},[49,866,591],{"class":76},[49,868,349],{"class":55},[49,870,561],{"class":59},[49,872,165],{"class":76},[49,874,566],{"class":55},[49,876,386],{"class":55},[49,878,879],{"class":76},")        ",[49,881,882],{"class":138},"\u002F\u002F небуферизованный\n",[49,884,885,887,889,891,893,895,897,900,902,905],{"class":51,"line":63},[49,886,591],{"class":76},[49,888,349],{"class":55},[49,890,561],{"class":59},[49,892,165],{"class":76},[49,894,566],{"class":55},[49,896,386],{"class":55},[49,898,899],{"class":76},", ",[49,901,488],{"class":352},[49,903,904],{"class":76},")    ",[49,906,907],{"class":138},"\u002F\u002F буферизованный на 10 элементов\n",[248,909,911],{"id":910},"небуферизованный-канал-рандеву","Небуферизованный канал — рандеву",[16,913,914,915,918],{},"Небуферизованный канал синхронизирует отправителя и получателя: ",[325,916,917],{},"отправка блокируется до тех пор, пока получатель не готов принять",", и наоборот:",[41,920,922],{"className":43,"code":921,"language":38,"meta":45,"style":45},"ch := make(chan int)\n\ngo func() {\n    fmt.Println(\"отправляю...\")\n    ch \u003C- 42 \u002F\u002F блокируется пока main не прочитает\n    fmt.Println(\"отправил\")\n}()\n\ntime.Sleep(time.Second)\nfmt.Println(\"получаю...\")\nval := \u003C-ch \u002F\u002F разблокирует горутину выше\nfmt.Println(\"получил:\", val)\n\n\u002F\u002F Вывод:\n\u002F\u002F отправляю...\n\u002F\u002F получаю...\n\u002F\u002F отправил\n\u002F\u002F получил: 42\n",[36,923,924,940,944,952,965,978,991,996,1000,1010,1024,1038,1052,1056,1061,1066,1071,1076],{"__ignoreMap":45},[49,925,926,928,930,932,934,936,938],{"class":51,"line":52},[49,927,591],{"class":76},[49,929,349],{"class":55},[49,931,561],{"class":59},[49,933,165],{"class":76},[49,935,566],{"class":55},[49,937,386],{"class":55},[49,939,106],{"class":76},[49,941,942],{"class":51,"line":63},[49,943,67],{"emptyLinePlaceholder":66},[49,945,946,948,950],{"class":51,"line":70},[49,947,38],{"class":55},[49,949,147],{"class":55},[49,951,123],{"class":76},[49,953,954,956,958,960,963],{"class":51,"line":80},[49,955,222],{"class":76},[49,957,162],{"class":59},[49,959,165],{"class":76},[49,961,962],{"class":83},"\"отправляю...\"",[49,964,106],{"class":76},[49,966,967,969,972,975],{"class":51,"line":93},[49,968,556],{"class":76},[49,970,971],{"class":55},"\u003C-",[49,973,974],{"class":352}," 42",[49,976,977],{"class":138}," \u002F\u002F блокируется пока main не прочитает\n",[49,979,980,982,984,986,989],{"class":51,"line":103},[49,981,222],{"class":76},[49,983,162],{"class":59},[49,985,165],{"class":76},[49,987,988],{"class":83},"\"отправил\"",[49,990,106],{"class":76},[49,992,993],{"class":51,"line":109},[49,994,995],{"class":76},"}()\n",[49,997,998],{"class":51,"line":114},[49,999,67],{"emptyLinePlaceholder":66},[49,1001,1002,1005,1007],{"class":51,"line":126},[49,1003,1004],{"class":76},"time.",[49,1006,190],{"class":59},[49,1008,1009],{"class":76},"(time.Second)\n",[49,1011,1012,1015,1017,1019,1022],{"class":51,"line":142},[49,1013,1014],{"class":76},"fmt.",[49,1016,162],{"class":59},[49,1018,165],{"class":76},[49,1020,1021],{"class":83},"\"получаю...\"",[49,1023,106],{"class":76},[49,1025,1026,1029,1031,1033,1035],{"class":51,"line":156},[49,1027,1028],{"class":76},"val ",[49,1030,349],{"class":55},[49,1032,588],{"class":55},[49,1034,591],{"class":76},[49,1036,1037],{"class":138},"\u002F\u002F разблокирует горутину выше\n",[49,1039,1040,1042,1044,1046,1049],{"class":51,"line":173},[49,1041,1014],{"class":76},[49,1043,162],{"class":59},[49,1045,165],{"class":76},[49,1047,1048],{"class":83},"\"получил:\"",[49,1050,1051],{"class":76},", val)\n",[49,1053,1054],{"class":51,"line":179},[49,1055,67],{"emptyLinePlaceholder":66},[49,1057,1058],{"class":51,"line":184},[49,1059,1060],{"class":138},"\u002F\u002F Вывод:\n",[49,1062,1063],{"class":51,"line":199},[49,1064,1065],{"class":138},"\u002F\u002F отправляю...\n",[49,1067,1068],{"class":51,"line":205},[49,1069,1070],{"class":138},"\u002F\u002F получаю...\n",[49,1072,1073],{"class":51,"line":210},[49,1074,1075],{"class":138},"\u002F\u002F отправил\n",[49,1077,1078],{"class":51,"line":219},[49,1079,1080],{"class":138},"\u002F\u002F получил: 42\n",[16,1082,1083],{},"Это называется рандеву (rendezvous) — встреча двух горутин в точке передачи данных. Именно поэтому небуферизованные каналы — мощный инструмент синхронизации.",[248,1085,1087],{"id":1086},"буферизованный-канал-асинхронная-очередь","Буферизованный канал — асинхронная очередь",[16,1089,1090],{},"Буферизованный канал позволяет отправить до N элементов не блокируясь, пока буфер не заполнен:",[41,1092,1094],{"className":43,"code":1093,"language":38,"meta":45,"style":45},"ch := make(chan int, 3)\n\nch \u003C- 1 \u002F\u002F не блокируется\nch \u003C- 2 \u002F\u002F не блокируется\nch \u003C- 3 \u002F\u002F не блокируется\n\u002F\u002F ch \u003C- 4 \u002F\u002F заблокировалось бы — буфер полон\n\nfmt.Println(\u003C-ch) \u002F\u002F 1\nfmt.Println(\u003C-ch) \u002F\u002F 2\nfmt.Println(\u003C-ch) \u002F\u002F 3\n",[36,1095,1096,1117,1121,1133,1144,1155,1163,1167,1183,1198],{"__ignoreMap":45},[49,1097,1098,1100,1102,1104,1106,1108,1110,1112,1115],{"class":51,"line":52},[49,1099,591],{"class":76},[49,1101,349],{"class":55},[49,1103,561],{"class":59},[49,1105,165],{"class":76},[49,1107,566],{"class":55},[49,1109,386],{"class":55},[49,1111,899],{"class":76},[49,1113,1114],{"class":352},"3",[49,1116,106],{"class":76},[49,1118,1119],{"class":51,"line":63},[49,1120,67],{"emptyLinePlaceholder":66},[49,1122,1123,1125,1127,1130],{"class":51,"line":70},[49,1124,591],{"class":76},[49,1126,971],{"class":55},[49,1128,1129],{"class":352}," 1",[49,1131,1132],{"class":138}," \u002F\u002F не блокируется\n",[49,1134,1135,1137,1139,1142],{"class":51,"line":80},[49,1136,591],{"class":76},[49,1138,971],{"class":55},[49,1140,1141],{"class":352}," 2",[49,1143,1132],{"class":138},[49,1145,1146,1148,1150,1153],{"class":51,"line":93},[49,1147,591],{"class":76},[49,1149,971],{"class":55},[49,1151,1152],{"class":352}," 3",[49,1154,1132],{"class":138},[49,1156,1157,1160],{"class":51,"line":103},[49,1158,1159],{"class":138},"\u002F\u002F ch \u003C- 4",[49,1161,1162],{"class":138}," \u002F\u002F заблокировалось бы — буфер полон\n",[49,1164,1165],{"class":51,"line":109},[49,1166,67],{"emptyLinePlaceholder":66},[49,1168,1169,1171,1173,1175,1177,1180],{"class":51,"line":114},[49,1170,1014],{"class":76},[49,1172,162],{"class":59},[49,1174,165],{"class":76},[49,1176,971],{"class":55},[49,1178,1179],{"class":76},"ch) ",[49,1181,1182],{"class":138},"\u002F\u002F 1\n",[49,1184,1185,1187,1189,1191,1193,1195],{"class":51,"line":126},[49,1186,1014],{"class":76},[49,1188,162],{"class":59},[49,1190,165],{"class":76},[49,1192,971],{"class":55},[49,1194,1179],{"class":76},[49,1196,1197],{"class":138},"\u002F\u002F 2\n",[49,1199,1200,1202,1204,1206,1208,1210],{"class":51,"line":142},[49,1201,1014],{"class":76},[49,1203,162],{"class":59},[49,1205,165],{"class":76},[49,1207,971],{"class":55},[49,1209,1179],{"class":76},[49,1211,1212],{"class":138},"\u002F\u002F 3\n",[16,1214,1215],{},"Буферизованный канал ведёт себя как очередь FIFO. Полезен когда производитель и потребитель работают с разной скоростью и нужно сгладить пики.",[248,1217,1219],{"id":1218},"закрытие-канала","Закрытие канала",[16,1221,1222,1223,1226],{},"Канал закрывается через ",[36,1224,1225],{},"close",". Закрытый канал:",[1228,1229,1230,1234,1237],"ul",{},[1231,1232,1233],"li",{},"Возвращает zero value немедленно при чтении",[1231,1235,1236],{},"Паникует при записи",[1231,1238,1239],{},"Паникует при повторном закрытии",[41,1241,1243],{"className":43,"code":1242,"language":38,"meta":45,"style":45},"ch := make(chan int, 3)\nch \u003C- 1\nch \u003C- 2\nclose(ch)\n\n\u002F\u002F Чтение из закрытого канала\nv, ok := \u003C-ch\nfmt.Println(v, ok) \u002F\u002F 1 true — данные ещё есть\n\nv, ok = \u003C-ch\nfmt.Println(v, ok) \u002F\u002F 2 true\n\nv, ok = \u003C-ch\nfmt.Println(v, ok) \u002F\u002F 0 false — канал закрыт и пуст\n\n\u002F\u002F range по каналу — завершится когда канал закрыт и пуст\nfor v := range ch {\n    fmt.Println(v)\n}\n",[36,1244,1245,1265,1274,1283,1290,1294,1299,1311,1323,1327,1338,1349,1353,1363,1374,1378,1383,1398,1407],{"__ignoreMap":45},[49,1246,1247,1249,1251,1253,1255,1257,1259,1261,1263],{"class":51,"line":52},[49,1248,591],{"class":76},[49,1250,349],{"class":55},[49,1252,561],{"class":59},[49,1254,165],{"class":76},[49,1256,566],{"class":55},[49,1258,386],{"class":55},[49,1260,899],{"class":76},[49,1262,1114],{"class":352},[49,1264,106],{"class":76},[49,1266,1267,1269,1271],{"class":51,"line":63},[49,1268,591],{"class":76},[49,1270,971],{"class":55},[49,1272,1273],{"class":352}," 1\n",[49,1275,1276,1278,1280],{"class":51,"line":70},[49,1277,591],{"class":76},[49,1279,971],{"class":55},[49,1281,1282],{"class":352}," 2\n",[49,1284,1285,1287],{"class":51,"line":80},[49,1286,1225],{"class":59},[49,1288,1289],{"class":76},"(ch)\n",[49,1291,1292],{"class":51,"line":93},[49,1293,67],{"emptyLinePlaceholder":66},[49,1295,1296],{"class":51,"line":103},[49,1297,1298],{"class":138},"\u002F\u002F Чтение из закрытого канала\n",[49,1300,1301,1304,1306,1308],{"class":51,"line":109},[49,1302,1303],{"class":76},"v, ok ",[49,1305,349],{"class":55},[49,1307,588],{"class":55},[49,1309,1310],{"class":76},"ch\n",[49,1312,1313,1315,1317,1320],{"class":51,"line":114},[49,1314,1014],{"class":76},[49,1316,162],{"class":59},[49,1318,1319],{"class":76},"(v, ok) ",[49,1321,1322],{"class":138},"\u002F\u002F 1 true — данные ещё есть\n",[49,1324,1325],{"class":51,"line":126},[49,1326,67],{"emptyLinePlaceholder":66},[49,1328,1329,1331,1334,1336],{"class":51,"line":142},[49,1330,1303],{"class":76},[49,1332,1333],{"class":55},"=",[49,1335,588],{"class":55},[49,1337,1310],{"class":76},[49,1339,1340,1342,1344,1346],{"class":51,"line":156},[49,1341,1014],{"class":76},[49,1343,162],{"class":59},[49,1345,1319],{"class":76},[49,1347,1348],{"class":138},"\u002F\u002F 2 true\n",[49,1350,1351],{"class":51,"line":173},[49,1352,67],{"emptyLinePlaceholder":66},[49,1354,1355,1357,1359,1361],{"class":51,"line":179},[49,1356,1303],{"class":76},[49,1358,1333],{"class":55},[49,1360,588],{"class":55},[49,1362,1310],{"class":76},[49,1364,1365,1367,1369,1371],{"class":51,"line":184},[49,1366,1014],{"class":76},[49,1368,162],{"class":59},[49,1370,1319],{"class":76},[49,1372,1373],{"class":138},"\u002F\u002F 0 false — канал закрыт и пуст\n",[49,1375,1376],{"class":51,"line":199},[49,1377,67],{"emptyLinePlaceholder":66},[49,1379,1380],{"class":51,"line":205},[49,1381,1382],{"class":138},"\u002F\u002F range по каналу — завершится когда канал закрыт и пуст\n",[49,1384,1385,1387,1390,1392,1395],{"class":51,"line":210},[49,1386,343],{"class":55},[49,1388,1389],{"class":76}," v ",[49,1391,349],{"class":55},[49,1393,1394],{"class":55}," range",[49,1396,1397],{"class":76}," ch {\n",[49,1399,1400,1402,1404],{"class":51,"line":219},[49,1401,222],{"class":76},[49,1403,162],{"class":59},[49,1405,1406],{"class":76},"(v)\n",[49,1408,1409],{"class":51,"line":234},[49,1410,202],{"class":76},[16,1412,1413,1416],{},[325,1414,1415],{},"Ключевое правило:"," закрывает канал тот, кто пишет — никогда читатель. Иначе запись в закрытый канал вызовет панику.",[248,1418,1420],{"id":1419},"направленные-каналы","Направленные каналы",[16,1422,1423],{},"Тип канала можно ограничить до send-only или receive-only. Это делает намерения явными и ловит ошибки на этапе компиляции:",[41,1425,1427],{"className":43,"code":1426,"language":38,"meta":45,"style":45},"package main\n\nimport \"fmt\"\n\nfunc producer(ch chan\u003C- int) { \u002F\u002F только запись\n    ch \u003C- 42\n    \u002F\u002F \u003C-ch \u002F\u002F Ошибка компиляции: receive from send-only channel\n}\n\nfunc consumer(ch \u003C-chan int) { \u002F\u002F только чтение\n    val := \u003C-ch\n    \u002F\u002F ch \u003C- 1 \u002F\u002F Ошибка компиляции: send to receive-only channel\n    fmt.Println(val)\n}\n\nfunc main() {\n    ch := make(chan int)\n    go producer(ch) \u002F\u002F двунаправленный неявно конвертируется\n    consumer(ch)\n}\n",[36,1428,1429,1435,1439,1450,1454,1477,1486,1494,1498,1502,1523,1534,1542,1550,1554,1558,1566,1582,1594,1601],{"__ignoreMap":45},[49,1430,1431,1433],{"class":51,"line":52},[49,1432,56],{"class":55},[49,1434,60],{"class":59},[49,1436,1437],{"class":51,"line":63},[49,1438,67],{"emptyLinePlaceholder":66},[49,1440,1441,1443,1446,1448],{"class":51,"line":70},[49,1442,73],{"class":55},[49,1444,1445],{"class":83}," \"",[49,1447,87],{"class":59},[49,1449,90],{"class":83},[49,1451,1452],{"class":51,"line":80},[49,1453,67],{"emptyLinePlaceholder":66},[49,1455,1456,1458,1461,1463,1466,1469,1471,1474],{"class":51,"line":93},[49,1457,117],{"class":55},[49,1459,1460],{"class":59}," producer",[49,1462,165],{"class":76},[49,1464,1465],{"class":382},"ch",[49,1467,1468],{"class":55}," chan\u003C-",[49,1470,386],{"class":55},[49,1472,1473],{"class":76},") { ",[49,1475,1476],{"class":138},"\u002F\u002F только запись\n",[49,1478,1479,1481,1483],{"class":51,"line":103},[49,1480,556],{"class":76},[49,1482,971],{"class":55},[49,1484,1485],{"class":352}," 42\n",[49,1487,1488,1491],{"class":51,"line":109},[49,1489,1490],{"class":138},"    \u002F\u002F \u003C-ch",[49,1492,1493],{"class":138}," \u002F\u002F Ошибка компиляции: receive from send-only channel\n",[49,1495,1496],{"class":51,"line":114},[49,1497,202],{"class":76},[49,1499,1500],{"class":51,"line":126},[49,1501,67],{"emptyLinePlaceholder":66},[49,1503,1504,1506,1509,1511,1513,1516,1518,1520],{"class":51,"line":142},[49,1505,117],{"class":55},[49,1507,1508],{"class":59}," consumer",[49,1510,165],{"class":76},[49,1512,1465],{"class":382},[49,1514,1515],{"class":55}," \u003C-chan",[49,1517,386],{"class":55},[49,1519,1473],{"class":76},[49,1521,1522],{"class":138},"\u002F\u002F только чтение\n",[49,1524,1525,1528,1530,1532],{"class":51,"line":156},[49,1526,1527],{"class":76},"    val ",[49,1529,349],{"class":55},[49,1531,588],{"class":55},[49,1533,1310],{"class":76},[49,1535,1536,1539],{"class":51,"line":173},[49,1537,1538],{"class":138},"    \u002F\u002F ch \u003C- 1",[49,1540,1541],{"class":138}," \u002F\u002F Ошибка компиляции: send to receive-only channel\n",[49,1543,1544,1546,1548],{"class":51,"line":179},[49,1545,222],{"class":76},[49,1547,162],{"class":59},[49,1549,603],{"class":76},[49,1551,1552],{"class":51,"line":184},[49,1553,202],{"class":76},[49,1555,1556],{"class":51,"line":199},[49,1557,67],{"emptyLinePlaceholder":66},[49,1559,1560,1562,1564],{"class":51,"line":205},[49,1561,117],{"class":55},[49,1563,120],{"class":59},[49,1565,123],{"class":76},[49,1567,1568,1570,1572,1574,1576,1578,1580],{"class":51,"line":210},[49,1569,556],{"class":76},[49,1571,349],{"class":55},[49,1573,561],{"class":59},[49,1575,165],{"class":76},[49,1577,566],{"class":55},[49,1579,386],{"class":55},[49,1581,106],{"class":76},[49,1583,1584,1586,1588,1591],{"class":51,"line":219},[49,1585,129],{"class":55},[49,1587,1460],{"class":59},[49,1589,1590],{"class":76},"(ch) ",[49,1592,1593],{"class":138},"\u002F\u002F двунаправленный неявно конвертируется\n",[49,1595,1596,1599],{"class":51,"line":234},[49,1597,1598],{"class":59},"    consumer",[49,1600,1289],{"class":76},[49,1602,1604],{"class":51,"line":1603},20,[49,1605,202],{"class":76},[25,1607],{},[28,1609,1611],{"id":1610},"select-мультиплексирование-каналов","select — мультиплексирование каналов",[16,1613,1614,1617,1618,1621],{},[36,1615,1616],{},"select"," — это ",[36,1619,1620],{},"switch"," для каналов. Он ждёт пока хотя бы один из кейсов станет готов, и выполняет его. Если несколько готовы одновременно — выбирает случайный:",[41,1623,1625],{"className":43,"code":1624,"language":38,"meta":45,"style":45},"ch1 := make(chan string)\nch2 := make(chan string)\n\ngo func() { ch1 \u003C- \"один\" }()\ngo func() { ch2 \u003C- \"два\" }()\n\nselect {\ncase msg := \u003C-ch1:\n    fmt.Println(\"из ch1:\", msg)\ncase msg := \u003C-ch2:\n    fmt.Println(\"из ch2:\", msg)\n}\n",[36,1626,1627,1645,1662,1666,1683,1699,1703,1709,1724,1738,1751,1764],{"__ignoreMap":45},[49,1628,1629,1632,1634,1636,1638,1640,1643],{"class":51,"line":52},[49,1630,1631],{"class":76},"ch1 ",[49,1633,349],{"class":55},[49,1635,561],{"class":59},[49,1637,165],{"class":76},[49,1639,566],{"class":55},[49,1641,1642],{"class":55}," string",[49,1644,106],{"class":76},[49,1646,1647,1650,1652,1654,1656,1658,1660],{"class":51,"line":63},[49,1648,1649],{"class":76},"ch2 ",[49,1651,349],{"class":55},[49,1653,561],{"class":59},[49,1655,165],{"class":76},[49,1657,566],{"class":55},[49,1659,1642],{"class":55},[49,1661,106],{"class":76},[49,1663,1664],{"class":51,"line":70},[49,1665,67],{"emptyLinePlaceholder":66},[49,1667,1668,1670,1672,1675,1677,1680],{"class":51,"line":80},[49,1669,38],{"class":55},[49,1671,147],{"class":55},[49,1673,1674],{"class":76},"() { ch1 ",[49,1676,971],{"class":55},[49,1678,1679],{"class":83}," \"один\"",[49,1681,1682],{"class":76}," }()\n",[49,1684,1685,1687,1689,1692,1694,1697],{"class":51,"line":93},[49,1686,38],{"class":55},[49,1688,147],{"class":55},[49,1690,1691],{"class":76},"() { ch2 ",[49,1693,971],{"class":55},[49,1695,1696],{"class":83}," \"два\"",[49,1698,1682],{"class":76},[49,1700,1701],{"class":51,"line":103},[49,1702,67],{"emptyLinePlaceholder":66},[49,1704,1705,1707],{"class":51,"line":109},[49,1706,1616],{"class":55},[49,1708,371],{"class":76},[49,1710,1711,1714,1717,1719,1721],{"class":51,"line":114},[49,1712,1713],{"class":55},"case",[49,1715,1716],{"class":76}," msg ",[49,1718,349],{"class":55},[49,1720,588],{"class":55},[49,1722,1723],{"class":76},"ch1:\n",[49,1725,1726,1728,1730,1732,1735],{"class":51,"line":126},[49,1727,222],{"class":76},[49,1729,162],{"class":59},[49,1731,165],{"class":76},[49,1733,1734],{"class":83},"\"из ch1:\"",[49,1736,1737],{"class":76},", msg)\n",[49,1739,1740,1742,1744,1746,1748],{"class":51,"line":142},[49,1741,1713],{"class":55},[49,1743,1716],{"class":76},[49,1745,349],{"class":55},[49,1747,588],{"class":55},[49,1749,1750],{"class":76},"ch2:\n",[49,1752,1753,1755,1757,1759,1762],{"class":51,"line":156},[49,1754,222],{"class":76},[49,1756,162],{"class":59},[49,1758,165],{"class":76},[49,1760,1761],{"class":83},"\"из ch2:\"",[49,1763,1737],{"class":76},[49,1765,1766],{"class":51,"line":173},[49,1767,202],{"class":76},[248,1769,1771],{"id":1770},"select-с-default-неблокирующие-операции","select с default — неблокирующие операции",[16,1773,1774,1777],{},[36,1775,1776],{},"default"," выполняется немедленно если ни один канал не готов:",[41,1779,1781],{"className":43,"code":1780,"language":38,"meta":45,"style":45},"ch := make(chan int)\n\nselect {\ncase v := \u003C-ch:\n    fmt.Println(\"получили:\", v)\ndefault:\n    fmt.Println(\"канал не готов, идём дальше\")\n}\n",[36,1782,1783,1799,1803,1809,1822,1836,1842,1855],{"__ignoreMap":45},[49,1784,1785,1787,1789,1791,1793,1795,1797],{"class":51,"line":52},[49,1786,591],{"class":76},[49,1788,349],{"class":55},[49,1790,561],{"class":59},[49,1792,165],{"class":76},[49,1794,566],{"class":55},[49,1796,386],{"class":55},[49,1798,106],{"class":76},[49,1800,1801],{"class":51,"line":63},[49,1802,67],{"emptyLinePlaceholder":66},[49,1804,1805,1807],{"class":51,"line":70},[49,1806,1616],{"class":55},[49,1808,371],{"class":76},[49,1810,1811,1813,1815,1817,1819],{"class":51,"line":80},[49,1812,1713],{"class":55},[49,1814,1389],{"class":76},[49,1816,349],{"class":55},[49,1818,588],{"class":55},[49,1820,1821],{"class":76},"ch:\n",[49,1823,1824,1826,1828,1830,1833],{"class":51,"line":93},[49,1825,222],{"class":76},[49,1827,162],{"class":59},[49,1829,165],{"class":76},[49,1831,1832],{"class":83},"\"получили:\"",[49,1834,1835],{"class":76},", v)\n",[49,1837,1838,1840],{"class":51,"line":103},[49,1839,1776],{"class":55},[49,1841,761],{"class":76},[49,1843,1844,1846,1848,1850,1853],{"class":51,"line":109},[49,1845,222],{"class":76},[49,1847,162],{"class":59},[49,1849,165],{"class":76},[49,1851,1852],{"class":83},"\"канал не готов, идём дальше\"",[49,1854,106],{"class":76},[49,1856,1857],{"class":51,"line":114},[49,1858,202],{"class":76},[248,1860,1862],{"id":1861},"таймаут-через-select","Таймаут через select",[16,1864,1865],{},"Классический паттерн ограничения времени ожидания:",[41,1867,1869],{"className":43,"code":1868,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"fmt\"\n    \"time\"\n)\n\ntype Result struct{ Value int }\n\nfunc doHeavyWork() Result {\n    time.Sleep(100 * time.Millisecond)\n    return Result{Value: 42}\n}\n\nfunc main() {\n    ch := make(chan Result)\n    go func() {\n        ch \u003C- doHeavyWork()\n    }()\n\n    select {\n    case result := \u003C-ch:\n        fmt.Println(\"результат:\", result)\n    case \u003C-time.After(5 * time.Second):\n        fmt.Println(\"таймаут — работа заняла слишком долго\")\n    }\n}\n",[36,1870,1871,1877,1881,1887,1895,1903,1907,1911,1931,1935,1949,1965,1980,1984,1988,1996,2012,2020,2031,2035,2039,2047,2062,2077,2099,2113,2119],{"__ignoreMap":45},[49,1872,1873,1875],{"class":51,"line":52},[49,1874,56],{"class":55},[49,1876,60],{"class":59},[49,1878,1879],{"class":51,"line":63},[49,1880,67],{"emptyLinePlaceholder":66},[49,1882,1883,1885],{"class":51,"line":70},[49,1884,73],{"class":55},[49,1886,77],{"class":76},[49,1888,1889,1891,1893],{"class":51,"line":80},[49,1890,84],{"class":83},[49,1892,87],{"class":59},[49,1894,90],{"class":83},[49,1896,1897,1899,1901],{"class":51,"line":93},[49,1898,84],{"class":83},[49,1900,98],{"class":59},[49,1902,90],{"class":83},[49,1904,1905],{"class":51,"line":103},[49,1906,106],{"class":76},[49,1908,1909],{"class":51,"line":109},[49,1910,67],{"emptyLinePlaceholder":66},[49,1912,1913,1916,1919,1922,1925,1928],{"class":51,"line":114},[49,1914,1915],{"class":55},"type",[49,1917,1918],{"class":59}," Result",[49,1920,1921],{"class":55}," struct",[49,1923,1924],{"class":76},"{ Value ",[49,1926,1927],{"class":55},"int",[49,1929,1930],{"class":76}," }\n",[49,1932,1933],{"class":51,"line":126},[49,1934,67],{"emptyLinePlaceholder":66},[49,1936,1937,1939,1942,1944,1947],{"class":51,"line":142},[49,1938,117],{"class":55},[49,1940,1941],{"class":59}," doHeavyWork",[49,1943,135],{"class":76},[49,1945,1946],{"class":59},"Result",[49,1948,371],{"class":76},[49,1950,1951,1953,1955,1957,1960,1962],{"class":51,"line":156},[49,1952,187],{"class":76},[49,1954,190],{"class":59},[49,1956,165],{"class":76},[49,1958,1959],{"class":352},"100",[49,1961,491],{"class":55},[49,1963,1964],{"class":76}," time.Millisecond)\n",[49,1966,1967,1970,1972,1975,1978],{"class":51,"line":173},[49,1968,1969],{"class":55},"    return",[49,1971,1918],{"class":59},[49,1973,1974],{"class":76},"{Value: ",[49,1976,1977],{"class":352},"42",[49,1979,202],{"class":76},[49,1981,1982],{"class":51,"line":179},[49,1983,202],{"class":76},[49,1985,1986],{"class":51,"line":184},[49,1987,67],{"emptyLinePlaceholder":66},[49,1989,1990,1992,1994],{"class":51,"line":199},[49,1991,117],{"class":55},[49,1993,120],{"class":59},[49,1995,123],{"class":76},[49,1997,1998,2000,2002,2004,2006,2008,2010],{"class":51,"line":205},[49,1999,556],{"class":76},[49,2001,349],{"class":55},[49,2003,561],{"class":59},[49,2005,165],{"class":76},[49,2007,566],{"class":55},[49,2009,1918],{"class":59},[49,2011,106],{"class":76},[49,2013,2014,2016,2018],{"class":51,"line":210},[49,2015,129],{"class":55},[49,2017,147],{"class":55},[49,2019,123],{"class":76},[49,2021,2022,2025,2027,2029],{"class":51,"line":219},[49,2023,2024],{"class":76},"        ch ",[49,2026,971],{"class":55},[49,2028,1941],{"class":59},[49,2030,769],{"class":76},[49,2032,2033],{"class":51,"line":234},[49,2034,176],{"class":76},[49,2036,2037],{"class":51,"line":1603},[49,2038,67],{"emptyLinePlaceholder":66},[49,2040,2042,2045],{"class":51,"line":2041},21,[49,2043,2044],{"class":55},"    select",[49,2046,371],{"class":76},[49,2048,2050,2053,2056,2058,2060],{"class":51,"line":2049},22,[49,2051,2052],{"class":55},"    case",[49,2054,2055],{"class":76}," result ",[49,2057,349],{"class":55},[49,2059,588],{"class":55},[49,2061,1821],{"class":76},[49,2063,2065,2067,2069,2071,2074],{"class":51,"line":2064},23,[49,2066,159],{"class":76},[49,2068,162],{"class":59},[49,2070,165],{"class":76},[49,2072,2073],{"class":83},"\"результат:\"",[49,2075,2076],{"class":76},", result)\n",[49,2078,2080,2082,2084,2086,2089,2091,2094,2096],{"class":51,"line":2079},24,[49,2081,2052],{"class":55},[49,2083,588],{"class":55},[49,2085,1004],{"class":76},[49,2087,2088],{"class":59},"After",[49,2090,165],{"class":76},[49,2092,2093],{"class":352},"5",[49,2095,491],{"class":55},[49,2097,2098],{"class":76}," time.Second):\n",[49,2100,2102,2104,2106,2108,2111],{"class":51,"line":2101},25,[49,2103,159],{"class":76},[49,2105,162],{"class":59},[49,2107,165],{"class":76},[49,2109,2110],{"class":83},"\"таймаут — работа заняла слишком долго\"",[49,2112,106],{"class":76},[49,2114,2116],{"class":51,"line":2115},26,[49,2117,2118],{"class":76},"    }\n",[49,2120,2122],{"class":51,"line":2121},27,[49,2123,202],{"class":76},[16,2125,2126,2129,2130,2132],{},[36,2127,2128],{},"time.After"," возвращает канал, в который придёт значение через указанное время. В паре с ",[36,2131,1616],{}," это идиоматичный таймаут в Go.",[248,2134,2136],{"id":2135},"бесконечный-цикл-с-select","Бесконечный цикл с select",[16,2138,2139],{},"Типичный паттерн для горутины-воркера:",[41,2141,2143],{"className":43,"code":2142,"language":38,"meta":45,"style":45},"package main\n\nimport \"fmt\"\n\ntype Job struct{ ID int }\ntype Result struct{ Value int }\n\nfunc process(j Job) Result { return Result{Value: j.ID * 2} }\n\nfunc worker(jobs \u003C-chan Job, results chan\u003C- Result, quit \u003C-chan struct{}) {\n    for {\n        select {\n        case job := \u003C-jobs:\n            results \u003C- process(job)\n        case \u003C-quit:\n            fmt.Println(\"воркер завершается\")\n            return\n        }\n    }\n}\n\nfunc main() {\n    jobs := make(chan Job, 3)\n    results := make(chan Result, 3)\n    quit := make(chan struct{})\n\n    go worker(jobs, results, quit)\n\n    jobs \u003C- Job{ID: 1}\n    jobs \u003C- Job{ID: 2}\n    jobs \u003C- Job{ID: 3}\n    close(jobs)\n\n    fmt.Println(\u003C-results)\n    fmt.Println(\u003C-results)\n    fmt.Println(\u003C-results)\n    close(quit)\n}\n",[36,2144,2145,2151,2155,2165,2169,2185,2199,2203,2241,2245,2282,2289,2296,2311,2323,2332,2346,2351,2355,2359,2363,2367,2375,2396,2417,2435,2439,2448,2453,2470,2486,2501,2510,2515,2529,2542,2555,2563],{"__ignoreMap":45},[49,2146,2147,2149],{"class":51,"line":52},[49,2148,56],{"class":55},[49,2150,60],{"class":59},[49,2152,2153],{"class":51,"line":63},[49,2154,67],{"emptyLinePlaceholder":66},[49,2156,2157,2159,2161,2163],{"class":51,"line":70},[49,2158,73],{"class":55},[49,2160,1445],{"class":83},[49,2162,87],{"class":59},[49,2164,90],{"class":83},[49,2166,2167],{"class":51,"line":80},[49,2168,67],{"emptyLinePlaceholder":66},[49,2170,2171,2173,2176,2178,2181,2183],{"class":51,"line":93},[49,2172,1915],{"class":55},[49,2174,2175],{"class":59}," Job",[49,2177,1921],{"class":55},[49,2179,2180],{"class":76},"{ ID ",[49,2182,1927],{"class":55},[49,2184,1930],{"class":76},[49,2186,2187,2189,2191,2193,2195,2197],{"class":51,"line":103},[49,2188,1915],{"class":55},[49,2190,1918],{"class":59},[49,2192,1921],{"class":55},[49,2194,1924],{"class":76},[49,2196,1927],{"class":55},[49,2198,1930],{"class":76},[49,2200,2201],{"class":51,"line":109},[49,2202,67],{"emptyLinePlaceholder":66},[49,2204,2205,2207,2210,2212,2215,2217,2220,2222,2225,2228,2230,2233,2236,2238],{"class":51,"line":114},[49,2206,117],{"class":55},[49,2208,2209],{"class":59}," process",[49,2211,165],{"class":76},[49,2213,2214],{"class":382},"j",[49,2216,2175],{"class":59},[49,2218,2219],{"class":76},") ",[49,2221,1946],{"class":59},[49,2223,2224],{"class":76}," { ",[49,2226,2227],{"class":55},"return",[49,2229,1918],{"class":59},[49,2231,2232],{"class":76},"{Value: j.ID ",[49,2234,2235],{"class":55},"*",[49,2237,1141],{"class":352},[49,2239,2240],{"class":76},"} }\n",[49,2242,2243],{"class":51,"line":126},[49,2244,67],{"emptyLinePlaceholder":66},[49,2246,2247,2249,2252,2254,2257,2259,2261,2263,2266,2268,2270,2272,2275,2277,2279],{"class":51,"line":142},[49,2248,117],{"class":55},[49,2250,2251],{"class":59}," worker",[49,2253,165],{"class":76},[49,2255,2256],{"class":382},"jobs",[49,2258,1515],{"class":55},[49,2260,2175],{"class":59},[49,2262,899],{"class":76},[49,2264,2265],{"class":382},"results",[49,2267,1468],{"class":55},[49,2269,1918],{"class":59},[49,2271,899],{"class":76},[49,2273,2274],{"class":382},"quit",[49,2276,1515],{"class":55},[49,2278,1921],{"class":55},[49,2280,2281],{"class":76},"{}) {\n",[49,2283,2284,2287],{"class":51,"line":156},[49,2285,2286],{"class":55},"    for",[49,2288,371],{"class":76},[49,2290,2291,2294],{"class":51,"line":173},[49,2292,2293],{"class":55},"        select",[49,2295,371],{"class":76},[49,2297,2298,2301,2304,2306,2308],{"class":51,"line":179},[49,2299,2300],{"class":55},"        case",[49,2302,2303],{"class":76}," job ",[49,2305,349],{"class":55},[49,2307,588],{"class":55},[49,2309,2310],{"class":76},"jobs:\n",[49,2312,2313,2316,2318,2320],{"class":51,"line":184},[49,2314,2315],{"class":76},"            results ",[49,2317,971],{"class":55},[49,2319,2209],{"class":59},[49,2321,2322],{"class":76},"(job)\n",[49,2324,2325,2327,2329],{"class":51,"line":199},[49,2326,2300],{"class":55},[49,2328,588],{"class":55},[49,2330,2331],{"class":76},"quit:\n",[49,2333,2334,2337,2339,2341,2344],{"class":51,"line":205},[49,2335,2336],{"class":76},"            fmt.",[49,2338,162],{"class":59},[49,2340,165],{"class":76},[49,2342,2343],{"class":83},"\"воркер завершается\"",[49,2345,106],{"class":76},[49,2347,2348],{"class":51,"line":210},[49,2349,2350],{"class":55},"            return\n",[49,2352,2353],{"class":51,"line":219},[49,2354,665],{"class":76},[49,2356,2357],{"class":51,"line":234},[49,2358,2118],{"class":76},[49,2360,2361],{"class":51,"line":1603},[49,2362,202],{"class":76},[49,2364,2365],{"class":51,"line":2041},[49,2366,67],{"emptyLinePlaceholder":66},[49,2368,2369,2371,2373],{"class":51,"line":2049},[49,2370,117],{"class":55},[49,2372,120],{"class":59},[49,2374,123],{"class":76},[49,2376,2377,2380,2382,2384,2386,2388,2390,2392,2394],{"class":51,"line":2064},[49,2378,2379],{"class":76},"    jobs ",[49,2381,349],{"class":55},[49,2383,561],{"class":59},[49,2385,165],{"class":76},[49,2387,566],{"class":55},[49,2389,2175],{"class":59},[49,2391,899],{"class":76},[49,2393,1114],{"class":352},[49,2395,106],{"class":76},[49,2397,2398,2401,2403,2405,2407,2409,2411,2413,2415],{"class":51,"line":2079},[49,2399,2400],{"class":76},"    results ",[49,2402,349],{"class":55},[49,2404,561],{"class":59},[49,2406,165],{"class":76},[49,2408,566],{"class":55},[49,2410,1918],{"class":59},[49,2412,899],{"class":76},[49,2414,1114],{"class":352},[49,2416,106],{"class":76},[49,2418,2419,2422,2424,2426,2428,2430,2432],{"class":51,"line":2101},[49,2420,2421],{"class":76},"    quit ",[49,2423,349],{"class":55},[49,2425,561],{"class":59},[49,2427,165],{"class":76},[49,2429,566],{"class":55},[49,2431,1921],{"class":55},[49,2433,2434],{"class":76},"{})\n",[49,2436,2437],{"class":51,"line":2115},[49,2438,67],{"emptyLinePlaceholder":66},[49,2440,2441,2443,2445],{"class":51,"line":2121},[49,2442,129],{"class":55},[49,2444,2251],{"class":59},[49,2446,2447],{"class":76},"(jobs, results, quit)\n",[49,2449,2451],{"class":51,"line":2450},28,[49,2452,67],{"emptyLinePlaceholder":66},[49,2454,2456,2458,2460,2462,2465,2468],{"class":51,"line":2455},29,[49,2457,2379],{"class":76},[49,2459,971],{"class":55},[49,2461,2175],{"class":59},[49,2463,2464],{"class":76},"{ID: ",[49,2466,2467],{"class":352},"1",[49,2469,202],{"class":76},[49,2471,2473,2475,2477,2479,2481,2484],{"class":51,"line":2472},30,[49,2474,2379],{"class":76},[49,2476,971],{"class":55},[49,2478,2175],{"class":59},[49,2480,2464],{"class":76},[49,2482,2483],{"class":352},"2",[49,2485,202],{"class":76},[49,2487,2489,2491,2493,2495,2497,2499],{"class":51,"line":2488},31,[49,2490,2379],{"class":76},[49,2492,971],{"class":55},[49,2494,2175],{"class":59},[49,2496,2464],{"class":76},[49,2498,1114],{"class":352},[49,2500,202],{"class":76},[49,2502,2504,2507],{"class":51,"line":2503},32,[49,2505,2506],{"class":59},"    close",[49,2508,2509],{"class":76},"(jobs)\n",[49,2511,2513],{"class":51,"line":2512},33,[49,2514,67],{"emptyLinePlaceholder":66},[49,2516,2518,2520,2522,2524,2526],{"class":51,"line":2517},34,[49,2519,222],{"class":76},[49,2521,162],{"class":59},[49,2523,165],{"class":76},[49,2525,971],{"class":55},[49,2527,2528],{"class":76},"results)\n",[49,2530,2532,2534,2536,2538,2540],{"class":51,"line":2531},35,[49,2533,222],{"class":76},[49,2535,162],{"class":59},[49,2537,165],{"class":76},[49,2539,971],{"class":55},[49,2541,2528],{"class":76},[49,2543,2545,2547,2549,2551,2553],{"class":51,"line":2544},36,[49,2546,222],{"class":76},[49,2548,162],{"class":59},[49,2550,165],{"class":76},[49,2552,971],{"class":55},[49,2554,2528],{"class":76},[49,2556,2558,2560],{"class":51,"line":2557},37,[49,2559,2506],{"class":59},[49,2561,2562],{"class":76},"(quit)\n",[49,2564,2566],{"class":51,"line":2565},38,[49,2567,202],{"class":76},[25,2569],{},[28,2571,2573],{"id":2572},"синхронизация-через-каналы","Синхронизация через каналы",[16,2575,2576],{},"Вернёмся к проблеме из начала статьи — правильное ожидание горутин:",[248,2578,2580],{"id":2579},"ожидание-завершения-через-канал-сигнал","Ожидание завершения через канал-сигнал",[41,2582,2584],{"className":43,"code":2583,"language":38,"meta":45,"style":45},"done := make(chan struct{})\n\ngo func() {\n    doWork()\n    close(done) \u002F\u002F сигнализируем о завершении\n}()\n\n\u003C-done \u002F\u002F блокируемся до закрытия канала\n",[36,2585,2586,2603,2607,2615,2622,2632,2636,2640],{"__ignoreMap":45},[49,2587,2588,2591,2593,2595,2597,2599,2601],{"class":51,"line":52},[49,2589,2590],{"class":76},"done ",[49,2592,349],{"class":55},[49,2594,561],{"class":59},[49,2596,165],{"class":76},[49,2598,566],{"class":55},[49,2600,1921],{"class":55},[49,2602,2434],{"class":76},[49,2604,2605],{"class":51,"line":63},[49,2606,67],{"emptyLinePlaceholder":66},[49,2608,2609,2611,2613],{"class":51,"line":70},[49,2610,38],{"class":55},[49,2612,147],{"class":55},[49,2614,123],{"class":76},[49,2616,2617,2620],{"class":51,"line":80},[49,2618,2619],{"class":59},"    doWork",[49,2621,769],{"class":76},[49,2623,2624,2626,2629],{"class":51,"line":93},[49,2625,2506],{"class":59},[49,2627,2628],{"class":76},"(done) ",[49,2630,2631],{"class":138},"\u002F\u002F сигнализируем о завершении\n",[49,2633,2634],{"class":51,"line":103},[49,2635,995],{"class":76},[49,2637,2638],{"class":51,"line":109},[49,2639,67],{"emptyLinePlaceholder":66},[49,2641,2642,2644,2646],{"class":51,"line":114},[49,2643,971],{"class":55},[49,2645,2590],{"class":76},[49,2647,2648],{"class":138},"\u002F\u002F блокируемся до закрытия канала\n",[16,2650,2651,2654],{},[36,2652,2653],{},"struct{}"," как тип канала — идиоматично для сигналов: нулевой размер, ничего не передаём, только факт события.",[248,2656,2658],{"id":2657},"waitgroup-для-нескольких-горутин","WaitGroup для нескольких горутин",[16,2660,2661,2662,2664],{},"Когда горутин несколько — ",[36,2663,245],{}," чище каналов:",[41,2666,2668],{"className":43,"code":2667,"language":38,"meta":45,"style":45},"var wg sync.WaitGroup\n\nfor i := 0; i \u003C 5; i++ {\n    wg.Add(1) \u002F\u002F инкремент до запуска горутины — важно!\n    go func(n int) {\n        defer wg.Done() \u002F\u002F декремент при завершении\n        fmt.Println(\"горутина\", n)\n    }(i)\n}\n\nwg.Wait() \u002F\u002F ждём пока счётчик не станет 0\n",[36,2669,2670,2686,2690,2713,2730,2744,2759,2773,2777,2781,2785],{"__ignoreMap":45},[49,2671,2672,2675,2678,2681,2683],{"class":51,"line":52},[49,2673,2674],{"class":55},"var",[49,2676,2677],{"class":76}," wg ",[49,2679,2680],{"class":59},"sync",[49,2682,703],{"class":76},[49,2684,2685],{"class":59},"WaitGroup\n",[49,2687,2688],{"class":51,"line":63},[49,2689,67],{"emptyLinePlaceholder":66},[49,2691,2692,2694,2696,2698,2700,2702,2704,2707,2709,2711],{"class":51,"line":70},[49,2693,343],{"class":55},[49,2695,346],{"class":76},[49,2697,349],{"class":55},[49,2699,353],{"class":352},[49,2701,356],{"class":76},[49,2703,359],{"class":55},[49,2705,2706],{"class":352}," 5",[49,2708,365],{"class":76},[49,2710,368],{"class":55},[49,2712,371],{"class":76},[49,2714,2715,2718,2721,2723,2725,2727],{"class":51,"line":80},[49,2716,2717],{"class":76},"    wg.",[49,2719,2720],{"class":59},"Add",[49,2722,165],{"class":76},[49,2724,2467],{"class":352},[49,2726,2219],{"class":76},[49,2728,2729],{"class":138},"\u002F\u002F инкремент до запуска горутины — важно!\n",[49,2731,2732,2734,2736,2738,2740,2742],{"class":51,"line":93},[49,2733,129],{"class":55},[49,2735,147],{"class":55},[49,2737,165],{"class":76},[49,2739,383],{"class":382},[49,2741,386],{"class":55},[49,2743,389],{"class":76},[49,2745,2746,2749,2752,2754,2756],{"class":51,"line":103},[49,2747,2748],{"class":55},"        defer",[49,2750,2751],{"class":76}," wg.",[49,2753,742],{"class":59},[49,2755,135],{"class":76},[49,2757,2758],{"class":138},"\u002F\u002F декремент при завершении\n",[49,2760,2761,2763,2765,2767,2770],{"class":51,"line":109},[49,2762,159],{"class":76},[49,2764,162],{"class":59},[49,2766,165],{"class":76},[49,2768,2769],{"class":83},"\"горутина\"",[49,2771,2772],{"class":76},", n)\n",[49,2774,2775],{"class":51,"line":114},[49,2776,399],{"class":76},[49,2778,2779],{"class":51,"line":126},[49,2780,202],{"class":76},[49,2782,2783],{"class":51,"line":142},[49,2784,67],{"emptyLinePlaceholder":66},[49,2786,2787,2790,2793,2795],{"class":51,"line":156},[49,2788,2789],{"class":76},"wg.",[49,2791,2792],{"class":59},"Wait",[49,2794,135],{"class":76},[49,2796,2797],{"class":138},"\u002F\u002F ждём пока счётчик не станет 0\n",[16,2799,2800,2803,2804,2806,2807,2810,2811,703],{},[36,2801,2802],{},"wg.Add(1)"," всегда должен вызываться до ",[36,2805,38],{},", а не внутри горутины. Иначе возможна гонка: ",[36,2808,2809],{},"wg.Wait()"," может отработать до того, как горутина успеет вызвать ",[36,2812,2720],{},[25,2814],{},[28,2816,2818],{"id":2817},"deadlock","Deadlock",[16,2820,2821],{},"Deadlock — ситуация, когда все горутины заблокированы и прогресс невозможен. Go детектирует это в рантайме:",[41,2823,2825],{"className":43,"code":2824,"language":38,"meta":45,"style":45},"func main() {\n    ch := make(chan int)\n    ch \u003C- 1 \u002F\u002F main блокируется — некому читать\n    \u002F\u002F fatal error: all goroutines are asleep - deadlock!\n}\n",[36,2826,2827,2835,2851,2862,2867],{"__ignoreMap":45},[49,2828,2829,2831,2833],{"class":51,"line":52},[49,2830,117],{"class":55},[49,2832,120],{"class":59},[49,2834,123],{"class":76},[49,2836,2837,2839,2841,2843,2845,2847,2849],{"class":51,"line":63},[49,2838,556],{"class":76},[49,2840,349],{"class":55},[49,2842,561],{"class":59},[49,2844,165],{"class":76},[49,2846,566],{"class":55},[49,2848,386],{"class":55},[49,2850,106],{"class":76},[49,2852,2853,2855,2857,2859],{"class":51,"line":70},[49,2854,556],{"class":76},[49,2856,971],{"class":55},[49,2858,1129],{"class":352},[49,2860,2861],{"class":138}," \u002F\u002F main блокируется — некому читать\n",[49,2863,2864],{"class":51,"line":80},[49,2865,2866],{"class":138},"    \u002F\u002F fatal error: all goroutines are asleep - deadlock!\n",[49,2868,2869],{"class":51,"line":93},[49,2870,202],{"class":76},[41,2872,2874],{"className":43,"code":2873,"language":38,"meta":45,"style":45},"\u002F\u002F Взаимный deadlock двух горутин\nch1 := make(chan int)\nch2 := make(chan int)\n\ngo func() {\n    ch1 \u003C- 1  \u002F\u002F ждёт ch2 \u003C- из второй горутины\n    \u003C-ch2\n}()\n\ngo func() {\n    ch2 \u003C- 2  \u002F\u002F ждёт ch1 \u003C- из первой горутины\n    \u003C-ch1\n}()\n\n\u002F\u002F Обе горутины ждут друг друга — deadlock\n",[36,2875,2876,2881,2897,2913,2917,2925,2937,2945,2949,2953,2961,2973,2980,2984,2988],{"__ignoreMap":45},[49,2877,2878],{"class":51,"line":52},[49,2879,2880],{"class":138},"\u002F\u002F Взаимный deadlock двух горутин\n",[49,2882,2883,2885,2887,2889,2891,2893,2895],{"class":51,"line":63},[49,2884,1631],{"class":76},[49,2886,349],{"class":55},[49,2888,561],{"class":59},[49,2890,165],{"class":76},[49,2892,566],{"class":55},[49,2894,386],{"class":55},[49,2896,106],{"class":76},[49,2898,2899,2901,2903,2905,2907,2909,2911],{"class":51,"line":70},[49,2900,1649],{"class":76},[49,2902,349],{"class":55},[49,2904,561],{"class":59},[49,2906,165],{"class":76},[49,2908,566],{"class":55},[49,2910,386],{"class":55},[49,2912,106],{"class":76},[49,2914,2915],{"class":51,"line":80},[49,2916,67],{"emptyLinePlaceholder":66},[49,2918,2919,2921,2923],{"class":51,"line":93},[49,2920,38],{"class":55},[49,2922,147],{"class":55},[49,2924,123],{"class":76},[49,2926,2927,2930,2932,2934],{"class":51,"line":103},[49,2928,2929],{"class":76},"    ch1 ",[49,2931,971],{"class":55},[49,2933,1129],{"class":352},[49,2935,2936],{"class":138},"  \u002F\u002F ждёт ch2 \u003C- из второй горутины\n",[49,2938,2939,2942],{"class":51,"line":109},[49,2940,2941],{"class":55},"    \u003C-",[49,2943,2944],{"class":76},"ch2\n",[49,2946,2947],{"class":51,"line":114},[49,2948,995],{"class":76},[49,2950,2951],{"class":51,"line":126},[49,2952,67],{"emptyLinePlaceholder":66},[49,2954,2955,2957,2959],{"class":51,"line":142},[49,2956,38],{"class":55},[49,2958,147],{"class":55},[49,2960,123],{"class":76},[49,2962,2963,2966,2968,2970],{"class":51,"line":156},[49,2964,2965],{"class":76},"    ch2 ",[49,2967,971],{"class":55},[49,2969,1141],{"class":352},[49,2971,2972],{"class":138},"  \u002F\u002F ждёт ch1 \u003C- из первой горутины\n",[49,2974,2975,2977],{"class":51,"line":173},[49,2976,2941],{"class":55},[49,2978,2979],{"class":76},"ch1\n",[49,2981,2982],{"class":51,"line":179},[49,2983,995],{"class":76},[49,2985,2986],{"class":51,"line":184},[49,2987,67],{"emptyLinePlaceholder":66},[49,2989,2990],{"class":51,"line":199},[49,2991,2992],{"class":138},"\u002F\u002F Обе горутины ждут друг друга — deadlock\n",[16,2994,2995,2996,2999],{},"Go детектирует только ",[325,2997,2998],{},"полный"," deadlock (все горутины заблокированы). Частичный — когда часть горутин заблокирована вечно — это утечка, и детектора для неё нет.",[25,3001],{},[28,3003,3005],{"id":3004},"распространённые-ошибки","Распространённые ошибки",[248,3007,3009],{"id":3008},"захват-переменной-цикла","Захват переменной цикла",[41,3011,3013],{"className":43,"code":3012,"language":38,"meta":418,"style":45},"\u002F\u002F Ошибка: все горутины захватывают одну переменную i,\n\u002F\u002F если она объявлена снаружи цикла\nvar i int\nfor i = 0; i \u003C 5; i++ {\n    go func() {\n        fmt.Println(i) \u002F\u002F скорее всего напечатает 5 5 5 5 5\n    }()\n}\n\n\u002F\u002F Правильно: передаём значение через аргумент\nfor i := 0; i \u003C 5; i++ {\n    go func(n int) {\n        fmt.Println(n) \u002F\u002F 0 1 2 3 4 (в неопределённом порядке)\n    }(i)\n}\n\n\u002F\u002F В Go 1.22+ это тоже правильно: переменная, объявленная\n\u002F\u002F прямо в for i := ..., создаётся заново на каждой итерации.\n",[36,3014,3015,3020,3025,3034,3056,3064,3076,3080,3084,3088,3093,3115,3129,3141,3145,3149,3153,3158],{"__ignoreMap":45},[49,3016,3017],{"class":51,"line":52},[49,3018,3019],{"class":138},"\u002F\u002F Ошибка: все горутины захватывают одну переменную i,\n",[49,3021,3022],{"class":51,"line":63},[49,3023,3024],{"class":138},"\u002F\u002F если она объявлена снаружи цикла\n",[49,3026,3027,3029,3031],{"class":51,"line":70},[49,3028,2674],{"class":55},[49,3030,346],{"class":76},[49,3032,3033],{"class":55},"int\n",[49,3035,3036,3038,3040,3042,3044,3046,3048,3050,3052,3054],{"class":51,"line":80},[49,3037,343],{"class":55},[49,3039,346],{"class":76},[49,3041,1333],{"class":55},[49,3043,353],{"class":352},[49,3045,356],{"class":76},[49,3047,359],{"class":55},[49,3049,2706],{"class":352},[49,3051,365],{"class":76},[49,3053,368],{"class":55},[49,3055,371],{"class":76},[49,3057,3058,3060,3062],{"class":51,"line":93},[49,3059,129],{"class":55},[49,3061,147],{"class":55},[49,3063,123],{"class":76},[49,3065,3066,3068,3070,3073],{"class":51,"line":103},[49,3067,159],{"class":76},[49,3069,162],{"class":59},[49,3071,3072],{"class":76},"(i) ",[49,3074,3075],{"class":138},"\u002F\u002F скорее всего напечатает 5 5 5 5 5\n",[49,3077,3078],{"class":51,"line":109},[49,3079,176],{"class":76},[49,3081,3082],{"class":51,"line":114},[49,3083,202],{"class":76},[49,3085,3086],{"class":51,"line":126},[49,3087,67],{"emptyLinePlaceholder":66},[49,3089,3090],{"class":51,"line":142},[49,3091,3092],{"class":138},"\u002F\u002F Правильно: передаём значение через аргумент\n",[49,3094,3095,3097,3099,3101,3103,3105,3107,3109,3111,3113],{"class":51,"line":156},[49,3096,343],{"class":55},[49,3098,346],{"class":76},[49,3100,349],{"class":55},[49,3102,353],{"class":352},[49,3104,356],{"class":76},[49,3106,359],{"class":55},[49,3108,2706],{"class":352},[49,3110,365],{"class":76},[49,3112,368],{"class":55},[49,3114,371],{"class":76},[49,3116,3117,3119,3121,3123,3125,3127],{"class":51,"line":173},[49,3118,129],{"class":55},[49,3120,147],{"class":55},[49,3122,165],{"class":76},[49,3124,383],{"class":382},[49,3126,386],{"class":55},[49,3128,389],{"class":76},[49,3130,3131,3133,3135,3138],{"class":51,"line":179},[49,3132,159],{"class":76},[49,3134,162],{"class":59},[49,3136,3137],{"class":76},"(n) ",[49,3139,3140],{"class":138},"\u002F\u002F 0 1 2 3 4 (в неопределённом порядке)\n",[49,3142,3143],{"class":51,"line":184},[49,3144,399],{"class":76},[49,3146,3147],{"class":51,"line":199},[49,3148,202],{"class":76},[49,3150,3151],{"class":51,"line":205},[49,3152,67],{"emptyLinePlaceholder":66},[49,3154,3155],{"class":51,"line":210},[49,3156,3157],{"class":138},"\u002F\u002F В Go 1.22+ это тоже правильно: переменная, объявленная\n",[49,3159,3160],{"class":51,"line":219},[49,3161,3162],{"class":138},"\u002F\u002F прямо в for i := ..., создаётся заново на каждой итерации.\n",[248,3164,3166],{"id":3165},"запись-в-закрытый-канал","Запись в закрытый канал",[41,3168,3170],{"className":43,"code":3169,"language":38,"meta":45,"style":45},"ch := make(chan int)\nclose(ch)\nch \u003C- 1 \u002F\u002F panic: send on closed channel\n",[36,3171,3172,3188,3194],{"__ignoreMap":45},[49,3173,3174,3176,3178,3180,3182,3184,3186],{"class":51,"line":52},[49,3175,591],{"class":76},[49,3177,349],{"class":55},[49,3179,561],{"class":59},[49,3181,165],{"class":76},[49,3183,566],{"class":55},[49,3185,386],{"class":55},[49,3187,106],{"class":76},[49,3189,3190,3192],{"class":51,"line":63},[49,3191,1225],{"class":59},[49,3193,1289],{"class":76},[49,3195,3196,3198,3200,3202],{"class":51,"line":70},[49,3197,591],{"class":76},[49,3199,971],{"class":55},[49,3201,1129],{"class":352},[49,3203,3204],{"class":138}," \u002F\u002F panic: send on closed channel\n",[248,3206,3208],{"id":3207},"паника-при-закрытии-уже-закрытого-канала","Паника при закрытии уже закрытого канала",[41,3210,3212],{"className":43,"code":3211,"language":38,"meta":45,"style":45},"ch := make(chan int)\nclose(ch)\nclose(ch) \u002F\u002F panic: close of closed channel\n\n\u002F\u002F Решение: sync.Once для гарантированно однократного закрытия\nvar once sync.Once\ncloseOnce := func() { once.Do(func() { close(ch) }) }\n",[36,3213,3214,3230,3236,3245,3249,3254,3268],{"__ignoreMap":45},[49,3215,3216,3218,3220,3222,3224,3226,3228],{"class":51,"line":52},[49,3217,591],{"class":76},[49,3219,349],{"class":55},[49,3221,561],{"class":59},[49,3223,165],{"class":76},[49,3225,566],{"class":55},[49,3227,386],{"class":55},[49,3229,106],{"class":76},[49,3231,3232,3234],{"class":51,"line":63},[49,3233,1225],{"class":59},[49,3235,1289],{"class":76},[49,3237,3238,3240,3242],{"class":51,"line":70},[49,3239,1225],{"class":59},[49,3241,1590],{"class":76},[49,3243,3244],{"class":138},"\u002F\u002F panic: close of closed channel\n",[49,3246,3247],{"class":51,"line":80},[49,3248,67],{"emptyLinePlaceholder":66},[49,3250,3251],{"class":51,"line":93},[49,3252,3253],{"class":138},"\u002F\u002F Решение: sync.Once для гарантированно однократного закрытия\n",[49,3255,3256,3258,3261,3263,3265],{"class":51,"line":103},[49,3257,2674],{"class":55},[49,3259,3260],{"class":76}," once ",[49,3262,2680],{"class":59},[49,3264,703],{"class":76},[49,3266,3267],{"class":59},"Once\n",[49,3269,3270,3273,3275,3277,3280,3283,3285,3287,3290,3292],{"class":51,"line":109},[49,3271,3272],{"class":76},"closeOnce ",[49,3274,349],{"class":55},[49,3276,147],{"class":55},[49,3278,3279],{"class":76},"() { once.",[49,3281,3282],{"class":59},"Do",[49,3284,165],{"class":76},[49,3286,117],{"class":55},[49,3288,3289],{"class":76},"() { ",[49,3291,1225],{"class":59},[49,3293,3294],{"class":76},"(ch) }) }\n",[25,3296],{},[28,3298,3300],{"id":3299},"вопросы-на-собеседовании","Вопросы на собеседовании",[16,3302,3303,3306,3309],{},[325,3304,3305],{},"Q: Чем горутина отличается от OS-потока?",[3307,3308],"br",{},"\nA: Горутина управляется рантаймом Go, а не ОС. Начальный стек — 2–8 КБ против 1–8 МБ у потока, создание — микросекунды против десятков микросекунд. Стек горутины растёт динамически. Тысячи горутин — норма, тысячи OS-потоков — проблема.",[16,3311,3312,3315,3317,3318,3320,3321,3324,3325,3328],{},[325,3313,3314],{},"Q: Что такое утечка горутины? Как обнаружить?",[3307,3316],{},"\nA: Горутина запущена, но никогда не завершится — заблокирована на канале, мьютексе или в бесконечном цикле без условия выхода. Держит память и стек. Обнаружить: ",[36,3319,792],{}," в тестах, ",[36,3322,3323],{},"runtime.NumGoroutine()"," в метриках, ",[36,3326,3327],{},"pprof"," в продакшне.",[16,3330,3331,3334,3336],{},[325,3332,3333],{},"Q: Чем буферизованный канал отличается от небуферизованного?",[3307,3335],{},"\nA: Небуферизованный — рандеву: отправитель блокируется пока получатель не готов, и наоборот. Буферизованный — асинхронная очередь FIFO: отправка не блокируется пока буфер не заполнен. Небуферизованный — строгая синхронизация, буферизованный — сглаживание пиков.",[16,3338,3339,3342,3344,3345,3348],{},[325,3340,3341],{},"Q: Что произойдёт при чтении из закрытого канала?",[3307,3343],{},"\nA: Немедленно вернётся zero value и ",[36,3346,3347],{},"false"," в двухаргументной форме. Если в канале ещё есть данные — сначала вернут их. Запись в закрытый канал — паника. Повторное закрытие — паника.",[16,3350,3351,3354,3356,3357,3359],{},[325,3352,3353],{},"Q: Кто должен закрывать канал?",[3307,3355],{},"\nA: Тот, кто пишет — отправитель. Читатель не знает, будет ли ещё запись, и закрытие с его стороны вызовет панику при следующей записи. Если отправителей несколько — используют ",[36,3358,245],{}," + отдельную горутину, которая ждёт всех и закрывает.",[16,3361,3362,3365,3367],{},[325,3363,3364],{},"Q: Что такое select? Что если несколько кейсов готовы одновременно?",[3307,3366],{},"\nA: Конструкция для мультиплексирования каналов — ждёт пока хотя бы один кейс станет готов. Если готовы несколько — выбирает случайный. Это намеренная рандомизация, чтобы не было систематического голодания одного канала.",[16,3369,3370,3373,3375,3376,3378,3379,3381,3382,3384,3385,3387,3388,3390],{},[325,3371,3372],{},"Q: Как сделать неблокирующую отправку или чтение из канала?",[3307,3374],{},"\nA: Через ",[36,3377,1616],{}," с ",[36,3380,1776],{},": если канал не готов — немедленно выполняется ",[36,3383,1776],{},". Без ",[36,3386,1776],{}," ",[36,3389,1616],{}," ждёт.",[16,3392,3393,3396,3398,3399,3401,3402,3404,3405,3408,3409,3411,3412,703],{},[325,3394,3395],{},"Q: Как правильно ждать завершения нескольких горутин?",[3307,3397],{},"\nA: ",[36,3400,245],{},": ",[36,3403,2802],{}," до запуска горутины, ",[36,3406,3407],{},"defer wg.Done()"," внутри, ",[36,3410,2809],{}," в основной горутине. Альтернатива для простых случаев — канал-сигнал ",[36,3413,3414],{},"chan struct{}",[16,3416,3417,3426,3428,3429,3431,3432,3434,3435,3437,3438,3440],{},[325,3418,3419,3420,3422,3423,3425],{},"Q: Почему ",[36,3421,2802],{}," нужно вызывать до ",[36,3424,38],{},", а не внутри горутины?",[3307,3427],{},"\nA: Гонка данных: ",[36,3430,2809],{}," может выполниться до того, как горутина успеет вызвать ",[36,3433,2720],{},". Если ",[36,3436,2720],{}," внутри горутины — счётчик может оставаться нулевым в момент ",[36,3439,2792],{},", и программа завершится не дождавшись горутин.",[16,3442,3443,3446,3448,3449,3452],{},[325,3444,3445],{},"Q: Что такое deadlock? Как Go его обнаруживает?",[3307,3447],{},"\nA: Ситуация когда все горутины заблокированы и прогресс невозможен. Go детектирует полный deadlock в рантайме: ",[36,3450,3451],{},"fatal error: all goroutines are asleep - deadlock!",". Частичный deadlock (часть горутин заблокирована вечно) — это утечка, детектора нет.",[16,3454,3455,3458,3460,3461,3464,3465,3468],{},[325,3456,3457],{},"Q: В чём проблема захвата переменной цикла в горутине?",[3307,3459],{},"\nA: Горутины захватывают переменную, а не снимок значения. Если переменная объявлена снаружи цикла, к моменту исполнения горутины она может уже содержать последнее значение. Решение: передавать значение как аргумент ",[36,3462,3463],{},"go func(n int){...}(i)",". В Go 1.22+ переменная, объявленная прямо в ",[36,3466,3467],{},"for i := ...",", создаётся заново на каждой итерации, поэтому старая ловушка проявляется только в legacy-коде или при внешней переменной.",[11,3470,3472],{"id":3471},"задачи-горутины-и-каналы","Задачи: Горутины и каналы",[25,3474],{},[16,3476,3477],{},[325,3478,3479],{},"Задача 1: Конкурентный сбор результатов",[16,3481,3482,3485],{},[325,3483,3484],{},"Уровень:"," Лёгкая",[16,3487,3488,3491],{},[325,3489,3490],{},"Что проверяет:"," базовый запуск горутин, сбор результатов через канал",[16,3493,3494,3497,3498,3501],{},[325,3495,3496],{},"Условие:"," Напиши функцию ",[36,3499,3500],{},"squareConcurrent(nums []int) []int"," которая возводит каждое число в квадрат конкурентно (каждое число в отдельной горутине) и возвращает результаты. Порядок результатов должен совпадать с порядком входных данных.",[16,3503,3504],{},[325,3505,3506],{},"Примеры:",[41,3508,3513],{"className":3509,"code":3511,"language":3512},[3510],"language-text","squareConcurrent([]int{1, 2, 3, 4, 5}) → [1 4 9 16 25]\n","text",[36,3514,3511],{"__ignoreMap":45},[16,3516,3517],{},[325,3518,3519],{},"Решение:",[41,3521,3523],{"className":43,"code":3522,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"fmt\"\n    \"sync\"\n)\n\nfunc squareConcurrent(nums []int) []int {\n    result := make([]int, len(nums))\n    var wg sync.WaitGroup\n\n    for i, n := range nums {\n        wg.Add(1)\n        go func(idx, val int) {\n            defer wg.Done()\n            result[idx] = val * val \u002F\u002F каждая горутина пишет в свой индекс\n        }(i, n)\n    }\n\n    wg.Wait()\n    return result\n}\n\nfunc main() {\n    fmt.Println(squareConcurrent([]int{1, 2, 3, 4, 5})) \u002F\u002F [1 4 9 16 25]\n}\n\n\u002F\u002F Безопасно без мьютекса: каждая горутина пишет\n\u002F\u002F в уникальный индекс — нет гонки данных.\n",[36,3524,3525,3531,3535,3541,3549,3557,3561,3565,3589,3611,3624,3628,3642,3655,3676,3687,3704,3709,3713,3717,3725,3732,3736,3740,3748,3791,3795,3799,3804],{"__ignoreMap":45},[49,3526,3527,3529],{"class":51,"line":52},[49,3528,56],{"class":55},[49,3530,60],{"class":59},[49,3532,3533],{"class":51,"line":63},[49,3534,67],{"emptyLinePlaceholder":66},[49,3536,3537,3539],{"class":51,"line":70},[49,3538,73],{"class":55},[49,3540,77],{"class":76},[49,3542,3543,3545,3547],{"class":51,"line":80},[49,3544,84],{"class":83},[49,3546,87],{"class":59},[49,3548,90],{"class":83},[49,3550,3551,3553,3555],{"class":51,"line":93},[49,3552,84],{"class":83},[49,3554,2680],{"class":59},[49,3556,90],{"class":83},[49,3558,3559],{"class":51,"line":103},[49,3560,106],{"class":76},[49,3562,3563],{"class":51,"line":109},[49,3564,67],{"emptyLinePlaceholder":66},[49,3566,3567,3569,3572,3574,3577,3580,3582,3585,3587],{"class":51,"line":114},[49,3568,117],{"class":55},[49,3570,3571],{"class":59}," squareConcurrent",[49,3573,165],{"class":76},[49,3575,3576],{"class":382},"nums",[49,3578,3579],{"class":76}," []",[49,3581,1927],{"class":55},[49,3583,3584],{"class":76},") []",[49,3586,1927],{"class":55},[49,3588,371],{"class":76},[49,3590,3591,3594,3596,3598,3601,3603,3605,3608],{"class":51,"line":126},[49,3592,3593],{"class":76},"    result ",[49,3595,349],{"class":55},[49,3597,561],{"class":59},[49,3599,3600],{"class":76},"([]",[49,3602,1927],{"class":55},[49,3604,899],{"class":76},[49,3606,3607],{"class":59},"len",[49,3609,3610],{"class":76},"(nums))\n",[49,3612,3613,3616,3618,3620,3622],{"class":51,"line":142},[49,3614,3615],{"class":55},"    var",[49,3617,2677],{"class":76},[49,3619,2680],{"class":59},[49,3621,703],{"class":76},[49,3623,2685],{"class":59},[49,3625,3626],{"class":51,"line":156},[49,3627,67],{"emptyLinePlaceholder":66},[49,3629,3630,3632,3635,3637,3639],{"class":51,"line":173},[49,3631,2286],{"class":55},[49,3633,3634],{"class":76}," i, n ",[49,3636,349],{"class":55},[49,3638,1394],{"class":55},[49,3640,3641],{"class":76}," nums {\n",[49,3643,3644,3647,3649,3651,3653],{"class":51,"line":179},[49,3645,3646],{"class":76},"        wg.",[49,3648,2720],{"class":59},[49,3650,165],{"class":76},[49,3652,2467],{"class":352},[49,3654,106],{"class":76},[49,3656,3657,3660,3662,3664,3667,3669,3672,3674],{"class":51,"line":184},[49,3658,3659],{"class":55},"        go",[49,3661,147],{"class":55},[49,3663,165],{"class":76},[49,3665,3666],{"class":382},"idx",[49,3668,899],{"class":76},[49,3670,3671],{"class":382},"val",[49,3673,386],{"class":55},[49,3675,389],{"class":76},[49,3677,3678,3681,3683,3685],{"class":51,"line":199},[49,3679,3680],{"class":55},"            defer",[49,3682,2751],{"class":76},[49,3684,742],{"class":59},[49,3686,769],{"class":76},[49,3688,3689,3692,3694,3697,3699,3701],{"class":51,"line":205},[49,3690,3691],{"class":76},"            result[idx] ",[49,3693,1333],{"class":55},[49,3695,3696],{"class":76}," val ",[49,3698,2235],{"class":55},[49,3700,3696],{"class":76},[49,3702,3703],{"class":138},"\u002F\u002F каждая горутина пишет в свой индекс\n",[49,3705,3706],{"class":51,"line":210},[49,3707,3708],{"class":76},"        }(i, n)\n",[49,3710,3711],{"class":51,"line":219},[49,3712,2118],{"class":76},[49,3714,3715],{"class":51,"line":234},[49,3716,67],{"emptyLinePlaceholder":66},[49,3718,3719,3721,3723],{"class":51,"line":1603},[49,3720,2717],{"class":76},[49,3722,2792],{"class":59},[49,3724,769],{"class":76},[49,3726,3727,3729],{"class":51,"line":2041},[49,3728,1969],{"class":55},[49,3730,3731],{"class":76}," result\n",[49,3733,3734],{"class":51,"line":2049},[49,3735,202],{"class":76},[49,3737,3738],{"class":51,"line":2064},[49,3739,67],{"emptyLinePlaceholder":66},[49,3741,3742,3744,3746],{"class":51,"line":2079},[49,3743,117],{"class":55},[49,3745,120],{"class":59},[49,3747,123],{"class":76},[49,3749,3750,3752,3754,3756,3759,3761,3763,3766,3768,3770,3772,3774,3776,3778,3781,3783,3785,3788],{"class":51,"line":2101},[49,3751,222],{"class":76},[49,3753,162],{"class":59},[49,3755,165],{"class":76},[49,3757,3758],{"class":59},"squareConcurrent",[49,3760,3600],{"class":76},[49,3762,1927],{"class":55},[49,3764,3765],{"class":76},"{",[49,3767,2467],{"class":352},[49,3769,899],{"class":76},[49,3771,2483],{"class":352},[49,3773,899],{"class":76},[49,3775,1114],{"class":352},[49,3777,899],{"class":76},[49,3779,3780],{"class":352},"4",[49,3782,899],{"class":76},[49,3784,2093],{"class":352},[49,3786,3787],{"class":76},"})) ",[49,3789,3790],{"class":138},"\u002F\u002F [1 4 9 16 25]\n",[49,3792,3793],{"class":51,"line":2115},[49,3794,202],{"class":76},[49,3796,3797],{"class":51,"line":2121},[49,3798,67],{"emptyLinePlaceholder":66},[49,3800,3801],{"class":51,"line":2450},[49,3802,3803],{"class":138},"\u002F\u002F Безопасно без мьютекса: каждая горутина пишет\n",[49,3805,3806],{"class":51,"line":2455},[49,3807,3808],{"class":138},"\u002F\u002F в уникальный индекс — нет гонки данных.\n",[25,3810],{},[16,3812,3813],{},[325,3814,3815],{},"Задача 2: Таймаут операции",[16,3817,3818,3820],{},[325,3819,3484],{}," Средняя",[16,3822,3823,3825],{},[325,3824,3490],{}," select с таймаутом, паттерн ограничения времени",[16,3827,3828,3497,3830,3833,3834,3837,3838,3840,3841,3844,3845,703],{},[325,3829,3496],{},[36,3831,3832],{},"withTimeout(timeout time.Duration, fn func() int) (int, error)"," которая запускает ",[36,3835,3836],{},"fn"," в горутине и возвращает результат. Если ",[36,3839,3836],{}," не завершилась за ",[36,3842,3843],{},"timeout"," — возвращает ошибку ",[36,3846,3847],{},"\"operation timed out\"",[16,3849,3850],{},[325,3851,3506],{},[41,3853,3856],{"className":3854,"code":3855,"language":3512},[3510],"withTimeout(1*time.Second, func() int {\n    time.Sleep(500*time.Millisecond)\n    return 42\n}) → (42, nil)\n\nwithTimeout(100*time.Millisecond, func() int {\n    time.Sleep(1*time.Second)\n    return 42\n}) → (0, \"operation timed out\")\n",[36,3857,3855],{"__ignoreMap":45},[16,3859,3860],{},[325,3861,3519],{},[41,3863,3865],{"className":43,"code":3864,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"errors\"\n    \"fmt\"\n    \"time\"\n)\n\nfunc withTimeout(timeout time.Duration, fn func() int) (int, error) {\n    ch := make(chan int, 1) \u002F\u002F буферизованный — горутина не зависнет\n\n    go func() {\n        ch \u003C- fn()\n    }()\n\n    select {\n    case result := \u003C-ch:\n        return result, nil\n    case \u003C-time.After(timeout):\n        return 0, errors.New(\"operation timed out\")\n    }\n}\n\nfunc main() {\n    result, err := withTimeout(time.Second, func() int {\n        time.Sleep(500 * time.Millisecond)\n        return 42\n    })\n    fmt.Println(result, err) \u002F\u002F 42 \u003Cnil>\n\n    result, err = withTimeout(100*time.Millisecond, func() int {\n        time.Sleep(time.Second)\n        return 42\n    })\n    fmt.Println(result, err) \u002F\u002F 0 operation timed out\n}\n\n\u002F\u002F Буферизованный канал важен: если таймаут сработал раньше\n\u002F\u002F и никто не читает из ch — горутина всё равно запишет\n\u002F\u002F и завершится. Без буфера — утечка горутины.\n",[36,3866,3867,3873,3877,3883,3892,3900,3908,3912,3916,3957,3980,3984,3992,4003,4007,4011,4017,4029,4040,4053,4071,4075,4079,4083,4091,4111,4126,4132,4137,4149,4153,4178,4186,4192,4196,4207,4211,4215,4220,4226],{"__ignoreMap":45},[49,3868,3869,3871],{"class":51,"line":52},[49,3870,56],{"class":55},[49,3872,60],{"class":59},[49,3874,3875],{"class":51,"line":63},[49,3876,67],{"emptyLinePlaceholder":66},[49,3878,3879,3881],{"class":51,"line":70},[49,3880,73],{"class":55},[49,3882,77],{"class":76},[49,3884,3885,3887,3890],{"class":51,"line":80},[49,3886,84],{"class":83},[49,3888,3889],{"class":59},"errors",[49,3891,90],{"class":83},[49,3893,3894,3896,3898],{"class":51,"line":93},[49,3895,84],{"class":83},[49,3897,87],{"class":59},[49,3899,90],{"class":83},[49,3901,3902,3904,3906],{"class":51,"line":103},[49,3903,84],{"class":83},[49,3905,98],{"class":59},[49,3907,90],{"class":83},[49,3909,3910],{"class":51,"line":109},[49,3911,106],{"class":76},[49,3913,3914],{"class":51,"line":114},[49,3915,67],{"emptyLinePlaceholder":66},[49,3917,3918,3920,3923,3925,3927,3930,3932,3935,3937,3939,3941,3943,3945,3948,3950,3952,3955],{"class":51,"line":126},[49,3919,117],{"class":55},[49,3921,3922],{"class":59}," withTimeout",[49,3924,165],{"class":76},[49,3926,3843],{"class":382},[49,3928,3929],{"class":59}," time",[49,3931,703],{"class":76},[49,3933,3934],{"class":59},"Duration",[49,3936,899],{"class":76},[49,3938,3836],{"class":382},[49,3940,147],{"class":55},[49,3942,135],{"class":76},[49,3944,1927],{"class":55},[49,3946,3947],{"class":76},") (",[49,3949,1927],{"class":55},[49,3951,899],{"class":76},[49,3953,3954],{"class":55},"error",[49,3956,389],{"class":76},[49,3958,3959,3961,3963,3965,3967,3969,3971,3973,3975,3977],{"class":51,"line":142},[49,3960,556],{"class":76},[49,3962,349],{"class":55},[49,3964,561],{"class":59},[49,3966,165],{"class":76},[49,3968,566],{"class":55},[49,3970,386],{"class":55},[49,3972,899],{"class":76},[49,3974,2467],{"class":352},[49,3976,2219],{"class":76},[49,3978,3979],{"class":138},"\u002F\u002F буферизованный — горутина не зависнет\n",[49,3981,3982],{"class":51,"line":156},[49,3983,67],{"emptyLinePlaceholder":66},[49,3985,3986,3988,3990],{"class":51,"line":173},[49,3987,129],{"class":55},[49,3989,147],{"class":55},[49,3991,123],{"class":76},[49,3993,3994,3996,3998,4001],{"class":51,"line":179},[49,3995,2024],{"class":76},[49,3997,971],{"class":55},[49,3999,4000],{"class":59}," fn",[49,4002,769],{"class":76},[49,4004,4005],{"class":51,"line":184},[49,4006,176],{"class":76},[49,4008,4009],{"class":51,"line":199},[49,4010,67],{"emptyLinePlaceholder":66},[49,4012,4013,4015],{"class":51,"line":205},[49,4014,2044],{"class":55},[49,4016,371],{"class":76},[49,4018,4019,4021,4023,4025,4027],{"class":51,"line":210},[49,4020,2052],{"class":55},[49,4022,2055],{"class":76},[49,4024,349],{"class":55},[49,4026,588],{"class":55},[49,4028,1821],{"class":76},[49,4030,4031,4034,4037],{"class":51,"line":219},[49,4032,4033],{"class":55},"        return",[49,4035,4036],{"class":76}," result, ",[49,4038,4039],{"class":352},"nil\n",[49,4041,4042,4044,4046,4048,4050],{"class":51,"line":234},[49,4043,2052],{"class":55},[49,4045,588],{"class":55},[49,4047,1004],{"class":76},[49,4049,2088],{"class":59},[49,4051,4052],{"class":76},"(timeout):\n",[49,4054,4055,4057,4059,4062,4065,4067,4069],{"class":51,"line":1603},[49,4056,4033],{"class":55},[49,4058,353],{"class":352},[49,4060,4061],{"class":76},", errors.",[49,4063,4064],{"class":59},"New",[49,4066,165],{"class":76},[49,4068,3847],{"class":83},[49,4070,106],{"class":76},[49,4072,4073],{"class":51,"line":2041},[49,4074,2118],{"class":76},[49,4076,4077],{"class":51,"line":2049},[49,4078,202],{"class":76},[49,4080,4081],{"class":51,"line":2064},[49,4082,67],{"emptyLinePlaceholder":66},[49,4084,4085,4087,4089],{"class":51,"line":2079},[49,4086,117],{"class":55},[49,4088,120],{"class":59},[49,4090,123],{"class":76},[49,4092,4093,4096,4098,4100,4103,4105,4107,4109],{"class":51,"line":2101},[49,4094,4095],{"class":76},"    result, err ",[49,4097,349],{"class":55},[49,4099,3922],{"class":59},[49,4101,4102],{"class":76},"(time.Second, ",[49,4104,117],{"class":55},[49,4106,135],{"class":76},[49,4108,1927],{"class":55},[49,4110,371],{"class":76},[49,4112,4113,4115,4117,4119,4122,4124],{"class":51,"line":2115},[49,4114,481],{"class":76},[49,4116,190],{"class":59},[49,4118,165],{"class":76},[49,4120,4121],{"class":352},"500",[49,4123,491],{"class":55},[49,4125,1964],{"class":76},[49,4127,4128,4130],{"class":51,"line":2121},[49,4129,4033],{"class":55},[49,4131,1485],{"class":352},[49,4133,4134],{"class":51,"line":2450},[49,4135,4136],{"class":76},"    })\n",[49,4138,4139,4141,4143,4146],{"class":51,"line":2455},[49,4140,222],{"class":76},[49,4142,162],{"class":59},[49,4144,4145],{"class":76},"(result, err) ",[49,4147,4148],{"class":138},"\u002F\u002F 42 \u003Cnil>\n",[49,4150,4151],{"class":51,"line":2472},[49,4152,67],{"emptyLinePlaceholder":66},[49,4154,4155,4157,4159,4161,4163,4165,4167,4170,4172,4174,4176],{"class":51,"line":2488},[49,4156,4095],{"class":76},[49,4158,1333],{"class":55},[49,4160,3922],{"class":59},[49,4162,165],{"class":76},[49,4164,1959],{"class":352},[49,4166,2235],{"class":55},[49,4168,4169],{"class":76},"time.Millisecond, ",[49,4171,117],{"class":55},[49,4173,135],{"class":76},[49,4175,1927],{"class":55},[49,4177,371],{"class":76},[49,4179,4180,4182,4184],{"class":51,"line":2503},[49,4181,481],{"class":76},[49,4183,190],{"class":59},[49,4185,1009],{"class":76},[49,4187,4188,4190],{"class":51,"line":2512},[49,4189,4033],{"class":55},[49,4191,1485],{"class":352},[49,4193,4194],{"class":51,"line":2517},[49,4195,4136],{"class":76},[49,4197,4198,4200,4202,4204],{"class":51,"line":2531},[49,4199,222],{"class":76},[49,4201,162],{"class":59},[49,4203,4145],{"class":76},[49,4205,4206],{"class":138},"\u002F\u002F 0 operation timed out\n",[49,4208,4209],{"class":51,"line":2544},[49,4210,202],{"class":76},[49,4212,4213],{"class":51,"line":2557},[49,4214,67],{"emptyLinePlaceholder":66},[49,4216,4217],{"class":51,"line":2565},[49,4218,4219],{"class":138},"\u002F\u002F Буферизованный канал важен: если таймаут сработал раньше\n",[49,4221,4223],{"class":51,"line":4222},39,[49,4224,4225],{"class":138},"\u002F\u002F и никто не читает из ch — горутина всё равно запишет\n",[49,4227,4229],{"class":51,"line":4228},40,[49,4230,4231],{"class":138},"\u002F\u002F и завершится. Без буфера — утечка горутины.\n",[25,4233],{},[16,4235,4236],{},[325,4237,4238],{},"Задача 3: Pipeline с отменой",[16,4240,4241,4243],{},[325,4242,3484],{}," Сложная",[16,4245,4246,4248],{},[325,4247,3490],{}," построение pipeline, корректная отмена через контекст",[16,4250,4251,4253,4254,4257,4258,4261,4262,4265,4266,703],{},[325,4252,3496],{}," Реализуй pipeline из трёх стадий: ",[36,4255,4256],{},"generate"," (числа от 1 до n), ",[36,4259,4260],{},"filter"," (только чётные), ",[36,4263,4264],{},"square"," (возведение в квадрат). Pipeline должен корректно завершаться при отмене контекста. Напиши функцию ",[36,4267,4268],{},"run(ctx context.Context, n int) []int",[16,4270,4271],{},[325,4272,3506],{},[41,4274,4277],{"className":4275,"code":4276,"language":3512},[3510],"run(ctx, 10) → [4 16 36 64 100]  \u002F\u002F чётные 2,4,6,8,10 → квадраты\nrun(ctx, 6)  → [4 16 36]\n",[36,4278,4276],{"__ignoreMap":45},[16,4280,4281,4284,4285,703],{},[325,4282,4283],{},"Подсказка:"," Каждая стадия — отдельная функция возвращающая канал. В каждом select слушай ",[36,4286,4287],{},"ctx.Done()",[16,4289,4290],{},[325,4291,3519],{},[41,4293,4295],{"className":43,"code":4294,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n)\n\nfunc generate(ctx context.Context, n int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for i := 1; i \u003C= n; i++ {\n            select {\n            case out \u003C- i:\n            case \u003C-ctx.Done():\n                return\n            }\n        }\n    }()\n    return out\n}\n\nfunc filter(ctx context.Context, in \u003C-chan int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for n := range in {\n            if n%2 == 0 {\n                select {\n                case out \u003C- n:\n                case \u003C-ctx.Done():\n                    return\n                }\n            }\n        }\n    }()\n    return out\n}\n\nfunc square(ctx context.Context, in \u003C-chan int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for n := range in {\n            select {\n            case out \u003C- n * n:\n            case \u003C-ctx.Done():\n                return\n            }\n        }\n    }()\n    return out\n}\n\nfunc run(ctx context.Context, n int) []int {\n    nums := generate(ctx, n)\n    evens := filter(ctx, nums)\n    squares := square(ctx, evens)\n\n    var result []int\n    for v := range squares {\n        result = append(result, v)\n    }\n    return result\n}\n\nfunc main() {\n    ctx := context.Background()\n    fmt.Println(run(ctx, 10)) \u002F\u002F [4 16 36 64 100]\n    fmt.Println(run(ctx, 6))  \u002F\u002F [4 16 36]\n}\n",[36,4296,4297,4303,4307,4313,4322,4330,4334,4338,4370,4387,4395,4405,4427,4433,4445,4458,4462,4466,4470,4474,4481,4485,4489,4523,4539,4547,4555,4569,4589,4596,4608,4620,4625,4630,4634,4638,4642,4648,4652,4656,4689,4706,4715,4724,4737,4744,4759,4772,4777,4782,4787,4792,4799,4804,4809,4839,4852,4865,4878,4883,4893,4907,4921,4926,4933,4938,4943,4952,4968,4991,5013],{"__ignoreMap":45},[49,4298,4299,4301],{"class":51,"line":52},[49,4300,56],{"class":55},[49,4302,60],{"class":59},[49,4304,4305],{"class":51,"line":63},[49,4306,67],{"emptyLinePlaceholder":66},[49,4308,4309,4311],{"class":51,"line":70},[49,4310,73],{"class":55},[49,4312,77],{"class":76},[49,4314,4315,4317,4320],{"class":51,"line":80},[49,4316,84],{"class":83},[49,4318,4319],{"class":59},"context",[49,4321,90],{"class":83},[49,4323,4324,4326,4328],{"class":51,"line":93},[49,4325,84],{"class":83},[49,4327,87],{"class":59},[49,4329,90],{"class":83},[49,4331,4332],{"class":51,"line":103},[49,4333,106],{"class":76},[49,4335,4336],{"class":51,"line":109},[49,4337,67],{"emptyLinePlaceholder":66},[49,4339,4340,4342,4345,4347,4349,4351,4353,4355,4357,4359,4361,4363,4366,4368],{"class":51,"line":114},[49,4341,117],{"class":55},[49,4343,4344],{"class":59}," generate",[49,4346,165],{"class":76},[49,4348,697],{"class":382},[49,4350,700],{"class":59},[49,4352,703],{"class":76},[49,4354,706],{"class":59},[49,4356,899],{"class":76},[49,4358,383],{"class":382},[49,4360,386],{"class":55},[49,4362,2219],{"class":76},[49,4364,4365],{"class":55},"\u003C-chan",[49,4367,386],{"class":55},[49,4369,371],{"class":76},[49,4371,4372,4375,4377,4379,4381,4383,4385],{"class":51,"line":126},[49,4373,4374],{"class":76},"    out ",[49,4376,349],{"class":55},[49,4378,561],{"class":59},[49,4380,165],{"class":76},[49,4382,566],{"class":55},[49,4384,386],{"class":55},[49,4386,106],{"class":76},[49,4388,4389,4391,4393],{"class":51,"line":142},[49,4390,129],{"class":55},[49,4392,147],{"class":55},[49,4394,123],{"class":76},[49,4396,4397,4399,4402],{"class":51,"line":156},[49,4398,2748],{"class":55},[49,4400,4401],{"class":59}," close",[49,4403,4404],{"class":76},"(out)\n",[49,4406,4407,4409,4411,4413,4415,4417,4420,4423,4425],{"class":51,"line":173},[49,4408,648],{"class":55},[49,4410,346],{"class":76},[49,4412,349],{"class":55},[49,4414,1129],{"class":352},[49,4416,356],{"class":76},[49,4418,4419],{"class":55},"\u003C=",[49,4421,4422],{"class":76}," n; i",[49,4424,368],{"class":55},[49,4426,371],{"class":76},[49,4428,4429,4431],{"class":51,"line":179},[49,4430,727],{"class":55},[49,4432,371],{"class":76},[49,4434,4435,4437,4440,4442],{"class":51,"line":184},[49,4436,734],{"class":55},[49,4438,4439],{"class":76}," out ",[49,4441,971],{"class":55},[49,4443,4444],{"class":76}," i:\n",[49,4446,4447,4449,4451,4453,4455],{"class":51,"line":199},[49,4448,734],{"class":55},[49,4450,588],{"class":55},[49,4452,739],{"class":76},[49,4454,742],{"class":59},[49,4456,4457],{"class":76},"():\n",[49,4459,4460],{"class":51,"line":205},[49,4461,753],{"class":55},[49,4463,4464],{"class":51,"line":210},[49,4465,774],{"class":76},[49,4467,4468],{"class":51,"line":219},[49,4469,665],{"class":76},[49,4471,4472],{"class":51,"line":234},[49,4473,176],{"class":76},[49,4475,4476,4478],{"class":51,"line":1603},[49,4477,1969],{"class":55},[49,4479,4480],{"class":76}," out\n",[49,4482,4483],{"class":51,"line":2041},[49,4484,202],{"class":76},[49,4486,4487],{"class":51,"line":2049},[49,4488,67],{"emptyLinePlaceholder":66},[49,4490,4491,4493,4496,4498,4500,4502,4504,4506,4508,4511,4513,4515,4517,4519,4521],{"class":51,"line":2064},[49,4492,117],{"class":55},[49,4494,4495],{"class":59}," filter",[49,4497,165],{"class":76},[49,4499,697],{"class":382},[49,4501,700],{"class":59},[49,4503,703],{"class":76},[49,4505,706],{"class":59},[49,4507,899],{"class":76},[49,4509,4510],{"class":382},"in",[49,4512,1515],{"class":55},[49,4514,386],{"class":55},[49,4516,2219],{"class":76},[49,4518,4365],{"class":55},[49,4520,386],{"class":55},[49,4522,371],{"class":76},[49,4524,4525,4527,4529,4531,4533,4535,4537],{"class":51,"line":2079},[49,4526,4374],{"class":76},[49,4528,349],{"class":55},[49,4530,561],{"class":59},[49,4532,165],{"class":76},[49,4534,566],{"class":55},[49,4536,386],{"class":55},[49,4538,106],{"class":76},[49,4540,4541,4543,4545],{"class":51,"line":2101},[49,4542,129],{"class":55},[49,4544,147],{"class":55},[49,4546,123],{"class":76},[49,4548,4549,4551,4553],{"class":51,"line":2115},[49,4550,2748],{"class":55},[49,4552,4401],{"class":59},[49,4554,4404],{"class":76},[49,4556,4557,4559,4562,4564,4566],{"class":51,"line":2121},[49,4558,648],{"class":55},[49,4560,4561],{"class":76}," n ",[49,4563,349],{"class":55},[49,4565,1394],{"class":55},[49,4567,4568],{"class":76}," in {\n",[49,4570,4571,4574,4577,4580,4582,4585,4587],{"class":51,"line":2450},[49,4572,4573],{"class":55},"            if",[49,4575,4576],{"class":76}," n",[49,4578,4579],{"class":55},"%",[49,4581,2483],{"class":352},[49,4583,4584],{"class":55}," ==",[49,4586,353],{"class":352},[49,4588,371],{"class":76},[49,4590,4591,4594],{"class":51,"line":2455},[49,4592,4593],{"class":55},"                select",[49,4595,371],{"class":76},[49,4597,4598,4601,4603,4605],{"class":51,"line":2472},[49,4599,4600],{"class":55},"                case",[49,4602,4439],{"class":76},[49,4604,971],{"class":55},[49,4606,4607],{"class":76}," n:\n",[49,4609,4610,4612,4614,4616,4618],{"class":51,"line":2488},[49,4611,4600],{"class":55},[49,4613,588],{"class":55},[49,4615,739],{"class":76},[49,4617,742],{"class":59},[49,4619,4457],{"class":76},[49,4621,4622],{"class":51,"line":2503},[49,4623,4624],{"class":55},"                    return\n",[49,4626,4627],{"class":51,"line":2512},[49,4628,4629],{"class":76},"                }\n",[49,4631,4632],{"class":51,"line":2517},[49,4633,774],{"class":76},[49,4635,4636],{"class":51,"line":2531},[49,4637,665],{"class":76},[49,4639,4640],{"class":51,"line":2544},[49,4641,176],{"class":76},[49,4643,4644,4646],{"class":51,"line":2557},[49,4645,1969],{"class":55},[49,4647,4480],{"class":76},[49,4649,4650],{"class":51,"line":2565},[49,4651,202],{"class":76},[49,4653,4654],{"class":51,"line":4222},[49,4655,67],{"emptyLinePlaceholder":66},[49,4657,4658,4660,4663,4665,4667,4669,4671,4673,4675,4677,4679,4681,4683,4685,4687],{"class":51,"line":4228},[49,4659,117],{"class":55},[49,4661,4662],{"class":59}," square",[49,4664,165],{"class":76},[49,4666,697],{"class":382},[49,4668,700],{"class":59},[49,4670,703],{"class":76},[49,4672,706],{"class":59},[49,4674,899],{"class":76},[49,4676,4510],{"class":382},[49,4678,1515],{"class":55},[49,4680,386],{"class":55},[49,4682,2219],{"class":76},[49,4684,4365],{"class":55},[49,4686,386],{"class":55},[49,4688,371],{"class":76},[49,4690,4692,4694,4696,4698,4700,4702,4704],{"class":51,"line":4691},41,[49,4693,4374],{"class":76},[49,4695,349],{"class":55},[49,4697,561],{"class":59},[49,4699,165],{"class":76},[49,4701,566],{"class":55},[49,4703,386],{"class":55},[49,4705,106],{"class":76},[49,4707,4709,4711,4713],{"class":51,"line":4708},42,[49,4710,129],{"class":55},[49,4712,147],{"class":55},[49,4714,123],{"class":76},[49,4716,4718,4720,4722],{"class":51,"line":4717},43,[49,4719,2748],{"class":55},[49,4721,4401],{"class":59},[49,4723,4404],{"class":76},[49,4725,4727,4729,4731,4733,4735],{"class":51,"line":4726},44,[49,4728,648],{"class":55},[49,4730,4561],{"class":76},[49,4732,349],{"class":55},[49,4734,1394],{"class":55},[49,4736,4568],{"class":76},[49,4738,4740,4742],{"class":51,"line":4739},45,[49,4741,727],{"class":55},[49,4743,371],{"class":76},[49,4745,4747,4749,4751,4753,4755,4757],{"class":51,"line":4746},46,[49,4748,734],{"class":55},[49,4750,4439],{"class":76},[49,4752,971],{"class":55},[49,4754,4561],{"class":76},[49,4756,2235],{"class":55},[49,4758,4607],{"class":76},[49,4760,4762,4764,4766,4768,4770],{"class":51,"line":4761},47,[49,4763,734],{"class":55},[49,4765,588],{"class":55},[49,4767,739],{"class":76},[49,4769,742],{"class":59},[49,4771,4457],{"class":76},[49,4773,4775],{"class":51,"line":4774},48,[49,4776,753],{"class":55},[49,4778,4780],{"class":51,"line":4779},49,[49,4781,774],{"class":76},[49,4783,4785],{"class":51,"line":4784},50,[49,4786,665],{"class":76},[49,4788,4790],{"class":51,"line":4789},51,[49,4791,176],{"class":76},[49,4793,4795,4797],{"class":51,"line":4794},52,[49,4796,1969],{"class":55},[49,4798,4480],{"class":76},[49,4800,4802],{"class":51,"line":4801},53,[49,4803,202],{"class":76},[49,4805,4807],{"class":51,"line":4806},54,[49,4808,67],{"emptyLinePlaceholder":66},[49,4810,4812,4814,4817,4819,4821,4823,4825,4827,4829,4831,4833,4835,4837],{"class":51,"line":4811},55,[49,4813,117],{"class":55},[49,4815,4816],{"class":59}," run",[49,4818,165],{"class":76},[49,4820,697],{"class":382},[49,4822,700],{"class":59},[49,4824,703],{"class":76},[49,4826,706],{"class":59},[49,4828,899],{"class":76},[49,4830,383],{"class":382},[49,4832,386],{"class":55},[49,4834,3584],{"class":76},[49,4836,1927],{"class":55},[49,4838,371],{"class":76},[49,4840,4842,4845,4847,4849],{"class":51,"line":4841},56,[49,4843,4844],{"class":76},"    nums ",[49,4846,349],{"class":55},[49,4848,4344],{"class":59},[49,4850,4851],{"class":76},"(ctx, n)\n",[49,4853,4855,4858,4860,4862],{"class":51,"line":4854},57,[49,4856,4857],{"class":76},"    evens ",[49,4859,349],{"class":55},[49,4861,4495],{"class":59},[49,4863,4864],{"class":76},"(ctx, nums)\n",[49,4866,4868,4871,4873,4875],{"class":51,"line":4867},58,[49,4869,4870],{"class":76},"    squares ",[49,4872,349],{"class":55},[49,4874,4662],{"class":59},[49,4876,4877],{"class":76},"(ctx, evens)\n",[49,4879,4881],{"class":51,"line":4880},59,[49,4882,67],{"emptyLinePlaceholder":66},[49,4884,4886,4888,4891],{"class":51,"line":4885},60,[49,4887,3615],{"class":55},[49,4889,4890],{"class":76}," result []",[49,4892,3033],{"class":55},[49,4894,4896,4898,4900,4902,4904],{"class":51,"line":4895},61,[49,4897,2286],{"class":55},[49,4899,1389],{"class":76},[49,4901,349],{"class":55},[49,4903,1394],{"class":55},[49,4905,4906],{"class":76}," squares {\n",[49,4908,4910,4913,4915,4918],{"class":51,"line":4909},62,[49,4911,4912],{"class":76},"        result ",[49,4914,1333],{"class":55},[49,4916,4917],{"class":59}," append",[49,4919,4920],{"class":76},"(result, v)\n",[49,4922,4924],{"class":51,"line":4923},63,[49,4925,2118],{"class":76},[49,4927,4929,4931],{"class":51,"line":4928},64,[49,4930,1969],{"class":55},[49,4932,3731],{"class":76},[49,4934,4936],{"class":51,"line":4935},65,[49,4937,202],{"class":76},[49,4939,4941],{"class":51,"line":4940},66,[49,4942,67],{"emptyLinePlaceholder":66},[49,4944,4946,4948,4950],{"class":51,"line":4945},67,[49,4947,117],{"class":55},[49,4949,120],{"class":59},[49,4951,123],{"class":76},[49,4953,4955,4958,4960,4963,4966],{"class":51,"line":4954},68,[49,4956,4957],{"class":76},"    ctx ",[49,4959,349],{"class":55},[49,4961,4962],{"class":76}," context.",[49,4964,4965],{"class":59},"Background",[49,4967,769],{"class":76},[49,4969,4971,4973,4975,4977,4980,4983,4985,4988],{"class":51,"line":4970},69,[49,4972,222],{"class":76},[49,4974,162],{"class":59},[49,4976,165],{"class":76},[49,4978,4979],{"class":59},"run",[49,4981,4982],{"class":76},"(ctx, ",[49,4984,488],{"class":352},[49,4986,4987],{"class":76},")) ",[49,4989,4990],{"class":138},"\u002F\u002F [4 16 36 64 100]\n",[49,4992,4994,4996,4998,5000,5002,5004,5007,5010],{"class":51,"line":4993},70,[49,4995,222],{"class":76},[49,4997,162],{"class":59},[49,4999,165],{"class":76},[49,5001,4979],{"class":59},[49,5003,4982],{"class":76},[49,5005,5006],{"class":352},"6",[49,5008,5009],{"class":76},"))  ",[49,5011,5012],{"class":138},"\u002F\u002F [4 16 36]\n",[49,5014,5016],{"class":51,"line":5015},71,[49,5017,202],{"class":76},[25,5019],{},[28,5021,5023],{"id":5022},"практика","Практика",[5025,5026,5028,5031,5048],"quiz",{"answer":2483,"id":5027,"xp":488},"concurrency-goroutines-q1",[16,5029,5030],{},"Чем небуферизованный канал отличается от буферизованного?",[5032,5033,5034],"template",{"v-slot:options":45},[1228,5035,5036,5039,5042,5045],{},[1231,5037,5038],{},"Небуферизованный работает быстрее — нет накладных расходов на буфер",[1231,5040,5041],{},"Небуферизованный блокирует отправителя до тех пор, пока получатель не готов принять (рандеву)",[1231,5043,5044],{},"Буферизованный гарантирует порядок доставки, небуферизованный — нет",[1231,5046,5047],{},"Разницы нет — оба работают одинаково",[5032,5049,5050],{"v-slot:explanation":45},[16,5051,5052,5053,5056,5057,5060,5061,5064],{},"Небуферизованный канал (",[36,5054,5055],{},"make(chan T)",") — это ",[325,5058,5059],{},"рандеву",": отправка блокируется пока получатель не готов, и наоборот. Буферизованный (",[36,5062,5063],{},"make(chan T, n)",") — асинхронная очередь FIFO: отправка не блокируется пока буфер не заполнен. Небуферизованный используется для строгой синхронизации, буферизованный — для сглаживания пиков.",[5066,5067,5071,5074,5283],"predict",{"answer":5068,"id":5069,"xp":5070},"отправляю...\\nполучаю...\\nотправил\\nполучил: 42","concurrency-goroutines-p1","15",[16,5072,5073],{},"Угадай точный вывод программы. Обрати внимание на порядок строк.",[5032,5075,5076],{"v-slot:code":45},[41,5077,5079],{"className":43,"code":5078,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"fmt\"\n    \"time\"\n)\n\nfunc main() {\n    ch := make(chan int)\n    done := make(chan struct{})\n\n    go func() {\n        defer close(done)\n        fmt.Println(\"отправляю...\")\n        ch \u003C- 42\n        fmt.Println(\"отправил\")\n    }()\n\n    time.Sleep(50 * time.Millisecond)\n    fmt.Println(\"получаю...\")\n    val := \u003C-ch\n    \u003C-done\n    fmt.Println(\"получил:\", val)\n}\n",[36,5080,5081,5087,5091,5097,5105,5113,5117,5121,5129,5145,5162,5166,5174,5183,5195,5203,5215,5219,5223,5238,5250,5260,5267,5279],{"__ignoreMap":45},[49,5082,5083,5085],{"class":51,"line":52},[49,5084,56],{"class":55},[49,5086,60],{"class":59},[49,5088,5089],{"class":51,"line":63},[49,5090,67],{"emptyLinePlaceholder":66},[49,5092,5093,5095],{"class":51,"line":70},[49,5094,73],{"class":55},[49,5096,77],{"class":76},[49,5098,5099,5101,5103],{"class":51,"line":80},[49,5100,84],{"class":83},[49,5102,87],{"class":59},[49,5104,90],{"class":83},[49,5106,5107,5109,5111],{"class":51,"line":93},[49,5108,84],{"class":83},[49,5110,98],{"class":59},[49,5112,90],{"class":83},[49,5114,5115],{"class":51,"line":103},[49,5116,106],{"class":76},[49,5118,5119],{"class":51,"line":109},[49,5120,67],{"emptyLinePlaceholder":66},[49,5122,5123,5125,5127],{"class":51,"line":114},[49,5124,117],{"class":55},[49,5126,120],{"class":59},[49,5128,123],{"class":76},[49,5130,5131,5133,5135,5137,5139,5141,5143],{"class":51,"line":126},[49,5132,556],{"class":76},[49,5134,349],{"class":55},[49,5136,561],{"class":59},[49,5138,165],{"class":76},[49,5140,566],{"class":55},[49,5142,386],{"class":55},[49,5144,106],{"class":76},[49,5146,5147,5150,5152,5154,5156,5158,5160],{"class":51,"line":142},[49,5148,5149],{"class":76},"    done ",[49,5151,349],{"class":55},[49,5153,561],{"class":59},[49,5155,165],{"class":76},[49,5157,566],{"class":55},[49,5159,1921],{"class":55},[49,5161,2434],{"class":76},[49,5163,5164],{"class":51,"line":156},[49,5165,67],{"emptyLinePlaceholder":66},[49,5167,5168,5170,5172],{"class":51,"line":173},[49,5169,129],{"class":55},[49,5171,147],{"class":55},[49,5173,123],{"class":76},[49,5175,5176,5178,5180],{"class":51,"line":179},[49,5177,2748],{"class":55},[49,5179,4401],{"class":59},[49,5181,5182],{"class":76},"(done)\n",[49,5184,5185,5187,5189,5191,5193],{"class":51,"line":184},[49,5186,159],{"class":76},[49,5188,162],{"class":59},[49,5190,165],{"class":76},[49,5192,962],{"class":83},[49,5194,106],{"class":76},[49,5196,5197,5199,5201],{"class":51,"line":199},[49,5198,2024],{"class":76},[49,5200,971],{"class":55},[49,5202,1485],{"class":352},[49,5204,5205,5207,5209,5211,5213],{"class":51,"line":205},[49,5206,159],{"class":76},[49,5208,162],{"class":59},[49,5210,165],{"class":76},[49,5212,988],{"class":83},[49,5214,106],{"class":76},[49,5216,5217],{"class":51,"line":210},[49,5218,176],{"class":76},[49,5220,5221],{"class":51,"line":219},[49,5222,67],{"emptyLinePlaceholder":66},[49,5224,5225,5227,5229,5231,5234,5236],{"class":51,"line":234},[49,5226,187],{"class":76},[49,5228,190],{"class":59},[49,5230,165],{"class":76},[49,5232,5233],{"class":352},"50",[49,5235,491],{"class":55},[49,5237,1964],{"class":76},[49,5239,5240,5242,5244,5246,5248],{"class":51,"line":1603},[49,5241,222],{"class":76},[49,5243,162],{"class":59},[49,5245,165],{"class":76},[49,5247,1021],{"class":83},[49,5249,106],{"class":76},[49,5251,5252,5254,5256,5258],{"class":51,"line":2041},[49,5253,1527],{"class":76},[49,5255,349],{"class":55},[49,5257,588],{"class":55},[49,5259,1310],{"class":76},[49,5261,5262,5264],{"class":51,"line":2049},[49,5263,2941],{"class":55},[49,5265,5266],{"class":76},"done\n",[49,5268,5269,5271,5273,5275,5277],{"class":51,"line":2064},[49,5270,222],{"class":76},[49,5272,162],{"class":59},[49,5274,165],{"class":76},[49,5276,1048],{"class":83},[49,5278,1051],{"class":76},[49,5280,5281],{"class":51,"line":2079},[49,5282,202],{"class":76},[5032,5284,5285],{"v-slot:hint":45},[16,5286,5287,5288,5291,5292,5295],{},"Горутина запускается и сразу печатает \"отправляю...\", затем блокируется на ",[36,5289,5290],{},"ch \u003C- 42",". Main спит 50мс, печатает \"получаю...\", читает из канала — разблокируя горутину. После чтения main ждёт ",[36,5293,5294],{},"done",", поэтому строка \"отправил\" гарантированно появится до \"получил: 42\".",[5297,5298,5302,5305,5390],"code-fix",{"expected":5299,"id":5300,"xp":5301},"горутина завершилась\\nвсё готово","concurrency-goroutines-cf1","25",[16,5303,5304],{},"Исправь код: программа должна дождаться завершения горутины перед выводом \"всё готово\". Сейчас \"всё готово\" может напечататься до горутины или горутина может не выполниться вообще.",[5032,5306,5307],{"v-slot:code":45},[41,5308,5310],{"className":43,"code":5309,"language":38,"meta":45,"style":45},"package main\n\nimport \"fmt\"\n\nfunc main() {\n    go func() {\n        fmt.Println(\"горутина завершилась\")\n    }()\n\n    fmt.Println(\"всё готово\")\n}\n",[36,5311,5312,5318,5322,5332,5336,5344,5352,5365,5369,5373,5386],{"__ignoreMap":45},[49,5313,5314,5316],{"class":51,"line":52},[49,5315,56],{"class":55},[49,5317,60],{"class":59},[49,5319,5320],{"class":51,"line":63},[49,5321,67],{"emptyLinePlaceholder":66},[49,5323,5324,5326,5328,5330],{"class":51,"line":70},[49,5325,73],{"class":55},[49,5327,1445],{"class":83},[49,5329,87],{"class":59},[49,5331,90],{"class":83},[49,5333,5334],{"class":51,"line":80},[49,5335,67],{"emptyLinePlaceholder":66},[49,5337,5338,5340,5342],{"class":51,"line":93},[49,5339,117],{"class":55},[49,5341,120],{"class":59},[49,5343,123],{"class":76},[49,5345,5346,5348,5350],{"class":51,"line":103},[49,5347,129],{"class":55},[49,5349,147],{"class":55},[49,5351,123],{"class":76},[49,5353,5354,5356,5358,5360,5363],{"class":51,"line":109},[49,5355,159],{"class":76},[49,5357,162],{"class":59},[49,5359,165],{"class":76},[49,5361,5362],{"class":83},"\"горутина завершилась\"",[49,5364,106],{"class":76},[49,5366,5367],{"class":51,"line":114},[49,5368,176],{"class":76},[49,5370,5371],{"class":51,"line":126},[49,5372,67],{"emptyLinePlaceholder":66},[49,5374,5375,5377,5379,5381,5384],{"class":51,"line":142},[49,5376,222],{"class":76},[49,5378,162],{"class":59},[49,5380,165],{"class":76},[49,5382,5383],{"class":83},"\"всё готово\"",[49,5385,106],{"class":76},[49,5387,5388],{"class":51,"line":156},[49,5389,202],{"class":76},[5032,5391,5392],{"v-slot:hints":45},[1228,5393,5394,5400,5410,5417],{},[1231,5395,5396,5397],{},"Создай канал-сигнал: ",[36,5398,5399],{},"done := make(chan struct{})",[1231,5401,5402,5403,5406,5407],{},"В горутине после работы закрой канал: ",[36,5404,5405],{},"close(done)"," или отправь значение: ",[36,5408,5409],{},"done \u003C- struct{}{}",[1231,5411,5412,5413,5416],{},"В main прочитай из канала: ",[36,5414,5415],{},"\u003C-done"," — это заблокирует выполнение до получения сигнала",[1231,5418,5419,5420],{},"Альтернатива: используй ",[36,5421,245],{},[5423,5424,5428,5441,5672],"code-task",{"expected":5425,"id":5426,"xp":5427},"1\\n4\\n9\\n16\\n25","concurrency-goroutines-ct1","20",[16,5429,5430,5431,5434,5435,5438,5439,703],{},"Реализуй функцию ",[36,5432,5433],{},"mapConcurrent",", которая применяет функцию ",[36,5436,5437],{},"f"," к каждому элементу слайса конкурентно (каждый элемент в отдельной горутине) и возвращает результаты в том же порядке. Используй ",[36,5440,245],{},[5032,5442,5443],{"v-slot:template":45},[41,5444,5446],{"className":43,"code":5445,"language":38,"meta":45,"style":45},"package main\n\nimport (\n    \"fmt\"\n    \"sync\"\n)\n\nfunc mapConcurrent(nums []int, f func(int) int) []int {\n    var wg sync.WaitGroup\n    _ = wg\n    return nil\n}\n\nfunc main() {\n    result := mapConcurrent(\n        []int{1, 2, 3, 4, 5},\n        func(x int) int { return x * x },\n    )\n    for _, v := range result {\n        fmt.Println(v)\n    }\n}\n",[36,5447,5448,5454,5458,5464,5472,5480,5484,5488,5523,5535,5545,5552,5556,5560,5568,5579,5609,5637,5642,5656,5664,5668],{"__ignoreMap":45},[49,5449,5450,5452],{"class":51,"line":52},[49,5451,56],{"class":55},[49,5453,60],{"class":59},[49,5455,5456],{"class":51,"line":63},[49,5457,67],{"emptyLinePlaceholder":66},[49,5459,5460,5462],{"class":51,"line":70},[49,5461,73],{"class":55},[49,5463,77],{"class":76},[49,5465,5466,5468,5470],{"class":51,"line":80},[49,5467,84],{"class":83},[49,5469,87],{"class":59},[49,5471,90],{"class":83},[49,5473,5474,5476,5478],{"class":51,"line":93},[49,5475,84],{"class":83},[49,5477,2680],{"class":59},[49,5479,90],{"class":83},[49,5481,5482],{"class":51,"line":103},[49,5483,106],{"class":76},[49,5485,5486],{"class":51,"line":109},[49,5487,67],{"emptyLinePlaceholder":66},[49,5489,5490,5492,5495,5497,5499,5501,5503,5505,5507,5509,5511,5513,5515,5517,5519,5521],{"class":51,"line":114},[49,5491,117],{"class":55},[49,5493,5494],{"class":59}," mapConcurrent",[49,5496,165],{"class":76},[49,5498,3576],{"class":382},[49,5500,3579],{"class":76},[49,5502,1927],{"class":55},[49,5504,899],{"class":76},[49,5506,5437],{"class":382},[49,5508,147],{"class":55},[49,5510,165],{"class":76},[49,5512,1927],{"class":55},[49,5514,2219],{"class":76},[49,5516,1927],{"class":55},[49,5518,3584],{"class":76},[49,5520,1927],{"class":55},[49,5522,371],{"class":76},[49,5524,5525,5527,5529,5531,5533],{"class":51,"line":126},[49,5526,3615],{"class":55},[49,5528,2677],{"class":76},[49,5530,2680],{"class":59},[49,5532,703],{"class":76},[49,5534,2685],{"class":59},[49,5536,5537,5540,5542],{"class":51,"line":142},[49,5538,5539],{"class":76},"    _ ",[49,5541,1333],{"class":55},[49,5543,5544],{"class":76}," wg\n",[49,5546,5547,5549],{"class":51,"line":156},[49,5548,1969],{"class":55},[49,5550,5551],{"class":352}," nil\n",[49,5553,5554],{"class":51,"line":173},[49,5555,202],{"class":76},[49,5557,5558],{"class":51,"line":179},[49,5559,67],{"emptyLinePlaceholder":66},[49,5561,5562,5564,5566],{"class":51,"line":184},[49,5563,117],{"class":55},[49,5565,120],{"class":59},[49,5567,123],{"class":76},[49,5569,5570,5572,5574,5576],{"class":51,"line":199},[49,5571,3593],{"class":76},[49,5573,349],{"class":55},[49,5575,5494],{"class":59},[49,5577,5578],{"class":76},"(\n",[49,5580,5581,5584,5586,5588,5590,5592,5594,5596,5598,5600,5602,5604,5606],{"class":51,"line":205},[49,5582,5583],{"class":76},"        []",[49,5585,1927],{"class":55},[49,5587,3765],{"class":76},[49,5589,2467],{"class":352},[49,5591,899],{"class":76},[49,5593,2483],{"class":352},[49,5595,899],{"class":76},[49,5597,1114],{"class":352},[49,5599,899],{"class":76},[49,5601,3780],{"class":352},[49,5603,899],{"class":76},[49,5605,2093],{"class":352},[49,5607,5608],{"class":76},"},\n",[49,5610,5611,5614,5616,5619,5621,5623,5625,5627,5629,5632,5634],{"class":51,"line":210},[49,5612,5613],{"class":55},"        func",[49,5615,165],{"class":76},[49,5617,5618],{"class":382},"x",[49,5620,386],{"class":55},[49,5622,2219],{"class":76},[49,5624,1927],{"class":55},[49,5626,2224],{"class":76},[49,5628,2227],{"class":55},[49,5630,5631],{"class":76}," x ",[49,5633,2235],{"class":55},[49,5635,5636],{"class":76}," x },\n",[49,5638,5639],{"class":51,"line":219},[49,5640,5641],{"class":76},"    )\n",[49,5643,5644,5646,5649,5651,5653],{"class":51,"line":234},[49,5645,2286],{"class":55},[49,5647,5648],{"class":76}," _, v ",[49,5650,349],{"class":55},[49,5652,1394],{"class":55},[49,5654,5655],{"class":76}," result {\n",[49,5657,5658,5660,5662],{"class":51,"line":1603},[49,5659,159],{"class":76},[49,5661,162],{"class":59},[49,5663,1406],{"class":76},[49,5665,5666],{"class":51,"line":2041},[49,5667,2118],{"class":76},[49,5669,5670],{"class":51,"line":2049},[49,5671,202],{"class":76},[5032,5673,5674],{"v-slot:hints":45},[1228,5675,5676,5683,5694,5704],{},[1231,5677,5678,5679,5682],{},"Создай ",[36,5680,5681],{},"result := make([]int, len(nums))"," — каждая горутина пишет в свой индекс",[1231,5684,5685,5686,899,5689,3404,5691,5693],{},"Используй ",[36,5687,5688],{},"var wg sync.WaitGroup",[36,5690,2802],{},[36,5692,3407],{}," внутри",[1231,5695,5696,5697,5700,5701,5703],{},"Передавай ",[36,5698,5699],{},"i"," и ",[36,5702,383],{}," как аргументы горутины, чтобы избежать захвата переменной цикла",[1231,5705,5706,5707,5709,5710],{},"В конце вызови ",[36,5708,2809],{}," перед ",[36,5711,5712],{},"return result",[5714,5715,5716],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":45,"searchDepth":63,"depth":63,"links":5718},[5719,5724,5730,5735,5739,5740,5745,5746],{"id":30,"depth":63,"text":31,"children":5720},[5721,5722,5723],{"id":250,"depth":70,"text":251},{"id":406,"depth":70,"text":407},{"id":523,"depth":70,"text":524},{"id":850,"depth":63,"text":851,"children":5725},[5726,5727,5728,5729],{"id":910,"depth":70,"text":911},{"id":1086,"depth":70,"text":1087},{"id":1218,"depth":70,"text":1219},{"id":1419,"depth":70,"text":1420},{"id":1610,"depth":63,"text":1611,"children":5731},[5732,5733,5734],{"id":1770,"depth":70,"text":1771},{"id":1861,"depth":70,"text":1862},{"id":2135,"depth":70,"text":2136},{"id":2572,"depth":63,"text":2573,"children":5736},[5737,5738],{"id":2579,"depth":70,"text":2580},{"id":2657,"depth":70,"text":2658},{"id":2817,"depth":63,"text":2818},{"id":3004,"depth":63,"text":3005,"children":5741},[5742,5743,5744],{"id":3008,"depth":70,"text":3009},{"id":3165,"depth":70,"text":3166},{"id":3207,"depth":70,"text":3208},{"id":3299,"depth":63,"text":3300},{"id":5022,"depth":63,"text":5023},1781458310657]