[{"data":1,"prerenderedAt":6923},["ShallowReactive",2],{"content-\u002F03-concurrency\u002F05-patterns":3},{"id":4,"title":5,"body":6,"description":17,"difficulty":6905,"extension":6906,"meta":6907,"module":6908,"navigation":192,"next":6909,"order":121,"path":6910,"prev":6911,"seo":6912,"slug":6913,"stem":6914,"tags":6915,"__hash__":6922},"content\u002F03-concurrency\u002F05-patterns\u002Findex.md","Паттерны конкурентности",{"type":7,"value":8,"toc":6887},"minimark",[9,14,18,21,26,29,620,627,632,635,1063,1066,1068,1072,1083,1804,1807,1809,1813,1816,2487,2491,2494,2865,2867,2871,2874,3039,3046,3216,3218,3222,3232,3522,3532,3536,3674,3679,3681,3685,3688,4164,4166,4170,4177,4407,4409,4413,4416,4722,4724,4728,4731,4805,4808,4810,4814,4823,4831,4854,4875,4889,4915,4931,4939,4941,4945,4947,4952,4958,4964,4974,4979,5450,5452,5457,5462,5467,5479,5483,6074,6076,6081,6086,6091,6104,6110,6114,6701,6703,6707,6710,6712,6717,6720,6726,6734,6736,6741,6744,6749,6755,6757,6762,6765,6770,6776,6778,6783,6786,6791,6797,6799,6804,6807,6812,6818,6820,6825,6828,6833,6839,6841,6846,6849,6854,6860,6862,6867,6870,6875,6881,6883],[10,11,13],"h1",{"id":12},"паттерны-конкурентности-в-go","Паттерны конкурентности в Go",[15,16,17],"p",{},"Это финальная статья блока по конкурентности. Горутины, каналы, sync-примитивы, контекст и планировщик — всё это инструменты. Паттерны — это проверенные способы их комбинировать для решения типовых задач. Здесь не будет новых концепций, только практика: берём то что знаем и складываем в рабочие конструкции.",[19,20],"hr",{},[22,23,25],"h2",{"id":24},"pipeline-конвейер-обработки","Pipeline — конвейер обработки",[15,27,28],{},"Pipeline — последовательность стадий, где каждая стадия читает данные из входного канала, трансформирует их и отдаёт в выходной. Стадии работают конкурентно — пока одна обрабатывает текущий элемент, следующая уже работает с предыдущим:",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-go shiki shiki-themes github-dark","\u002F\u002F Стадия 1: генератор — преобразует слайс в канал\nfunc generate(nums []int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for _, n := range nums {\n            out \u003C- n\n        }\n    }()\n    return out\n}\n\n\u002F\u002F Стадия 2: возведение в квадрат\nfunc square(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            out \u003C- n * n\n        }\n    }()\n    return out\n}\n\n\u002F\u002F Стадия 3: фильтрация чётных\nfunc filterEven(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                out \u003C- n\n            }\n        }\n    }()\n    return out\n}\n\nfunc main() {\n    \u002F\u002F Собираем конвейер\n    nums := generate([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})\n    squared := square(nums)\n    filtered := filterEven(squared)\n\n    for result := range filtered {\n        fmt.Println(result) \u002F\u002F 4, 16, 36, 64, 100\n    }\n}\n","go","",[37,38,39,48,85,107,119,131,148,160,166,172,181,187,194,200,226,243,252,261,276,290,295,300,307,312,317,323,347,364,373,382,395,419,429,435,440,445,452,457,462,472,478,547,560,573,578,594,609,615],"code",{"__ignoreMap":35},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sAwPA","\u002F\u002F Стадия 1: генератор — преобразует слайс в канал\n",[40,49,51,55,59,63,67,70,73,76,79,82],{"class":42,"line":50},2,[40,52,54],{"class":53},"snl16","func",[40,56,58],{"class":57},"svObZ"," generate",[40,60,62],{"class":61},"s95oV","(",[40,64,66],{"class":65},"s9osk","nums",[40,68,69],{"class":61}," []",[40,71,72],{"class":53},"int",[40,74,75],{"class":61},") ",[40,77,78],{"class":53},"\u003C-chan",[40,80,81],{"class":53}," int",[40,83,84],{"class":61}," {\n",[40,86,88,91,94,97,99,102,104],{"class":42,"line":87},3,[40,89,90],{"class":61},"    out ",[40,92,93],{"class":53},":=",[40,95,96],{"class":57}," make",[40,98,62],{"class":61},[40,100,101],{"class":53},"chan",[40,103,81],{"class":53},[40,105,106],{"class":61},")\n",[40,108,110,113,116],{"class":42,"line":109},4,[40,111,112],{"class":53},"    go",[40,114,115],{"class":53}," func",[40,117,118],{"class":61},"() {\n",[40,120,122,125,128],{"class":42,"line":121},5,[40,123,124],{"class":53},"        defer",[40,126,127],{"class":57}," close",[40,129,130],{"class":61},"(out)\n",[40,132,134,137,140,142,145],{"class":42,"line":133},6,[40,135,136],{"class":53},"        for",[40,138,139],{"class":61}," _, n ",[40,141,93],{"class":53},[40,143,144],{"class":53}," range",[40,146,147],{"class":61}," nums {\n",[40,149,151,154,157],{"class":42,"line":150},7,[40,152,153],{"class":61},"            out ",[40,155,156],{"class":53},"\u003C-",[40,158,159],{"class":61}," n\n",[40,161,163],{"class":42,"line":162},8,[40,164,165],{"class":61},"        }\n",[40,167,169],{"class":42,"line":168},9,[40,170,171],{"class":61},"    }()\n",[40,173,175,178],{"class":42,"line":174},10,[40,176,177],{"class":53},"    return",[40,179,180],{"class":61}," out\n",[40,182,184],{"class":42,"line":183},11,[40,185,186],{"class":61},"}\n",[40,188,190],{"class":42,"line":189},12,[40,191,193],{"emptyLinePlaceholder":192},true,"\n",[40,195,197],{"class":42,"line":196},13,[40,198,199],{"class":46},"\u002F\u002F Стадия 2: возведение в квадрат\n",[40,201,203,205,208,210,213,216,218,220,222,224],{"class":42,"line":202},14,[40,204,54],{"class":53},[40,206,207],{"class":57}," square",[40,209,62],{"class":61},[40,211,212],{"class":65},"in",[40,214,215],{"class":53}," \u003C-chan",[40,217,81],{"class":53},[40,219,75],{"class":61},[40,221,78],{"class":53},[40,223,81],{"class":53},[40,225,84],{"class":61},[40,227,229,231,233,235,237,239,241],{"class":42,"line":228},15,[40,230,90],{"class":61},[40,232,93],{"class":53},[40,234,96],{"class":57},[40,236,62],{"class":61},[40,238,101],{"class":53},[40,240,81],{"class":53},[40,242,106],{"class":61},[40,244,246,248,250],{"class":42,"line":245},16,[40,247,112],{"class":53},[40,249,115],{"class":53},[40,251,118],{"class":61},[40,253,255,257,259],{"class":42,"line":254},17,[40,256,124],{"class":53},[40,258,127],{"class":57},[40,260,130],{"class":61},[40,262,264,266,269,271,273],{"class":42,"line":263},18,[40,265,136],{"class":53},[40,267,268],{"class":61}," n ",[40,270,93],{"class":53},[40,272,144],{"class":53},[40,274,275],{"class":61}," in {\n",[40,277,279,281,283,285,288],{"class":42,"line":278},19,[40,280,153],{"class":61},[40,282,156],{"class":53},[40,284,268],{"class":61},[40,286,287],{"class":53},"*",[40,289,159],{"class":61},[40,291,293],{"class":42,"line":292},20,[40,294,165],{"class":61},[40,296,298],{"class":42,"line":297},21,[40,299,171],{"class":61},[40,301,303,305],{"class":42,"line":302},22,[40,304,177],{"class":53},[40,306,180],{"class":61},[40,308,310],{"class":42,"line":309},23,[40,311,186],{"class":61},[40,313,315],{"class":42,"line":314},24,[40,316,193],{"emptyLinePlaceholder":192},[40,318,320],{"class":42,"line":319},25,[40,321,322],{"class":46},"\u002F\u002F Стадия 3: фильтрация чётных\n",[40,324,326,328,331,333,335,337,339,341,343,345],{"class":42,"line":325},26,[40,327,54],{"class":53},[40,329,330],{"class":57}," filterEven",[40,332,62],{"class":61},[40,334,212],{"class":65},[40,336,215],{"class":53},[40,338,81],{"class":53},[40,340,75],{"class":61},[40,342,78],{"class":53},[40,344,81],{"class":53},[40,346,84],{"class":61},[40,348,350,352,354,356,358,360,362],{"class":42,"line":349},27,[40,351,90],{"class":61},[40,353,93],{"class":53},[40,355,96],{"class":57},[40,357,62],{"class":61},[40,359,101],{"class":53},[40,361,81],{"class":53},[40,363,106],{"class":61},[40,365,367,369,371],{"class":42,"line":366},28,[40,368,112],{"class":53},[40,370,115],{"class":53},[40,372,118],{"class":61},[40,374,376,378,380],{"class":42,"line":375},29,[40,377,124],{"class":53},[40,379,127],{"class":57},[40,381,130],{"class":61},[40,383,385,387,389,391,393],{"class":42,"line":384},30,[40,386,136],{"class":53},[40,388,268],{"class":61},[40,390,93],{"class":53},[40,392,144],{"class":53},[40,394,275],{"class":61},[40,396,398,401,404,407,411,414,417],{"class":42,"line":397},31,[40,399,400],{"class":53},"            if",[40,402,403],{"class":61}," n",[40,405,406],{"class":53},"%",[40,408,410],{"class":409},"sDLfK","2",[40,412,413],{"class":53}," ==",[40,415,416],{"class":409}," 0",[40,418,84],{"class":61},[40,420,422,425,427],{"class":42,"line":421},32,[40,423,424],{"class":61},"                out ",[40,426,156],{"class":53},[40,428,159],{"class":61},[40,430,432],{"class":42,"line":431},33,[40,433,434],{"class":61},"            }\n",[40,436,438],{"class":42,"line":437},34,[40,439,165],{"class":61},[40,441,443],{"class":42,"line":442},35,[40,444,171],{"class":61},[40,446,448,450],{"class":42,"line":447},36,[40,449,177],{"class":53},[40,451,180],{"class":61},[40,453,455],{"class":42,"line":454},37,[40,456,186],{"class":61},[40,458,460],{"class":42,"line":459},38,[40,461,193],{"emptyLinePlaceholder":192},[40,463,465,467,470],{"class":42,"line":464},39,[40,466,54],{"class":53},[40,468,469],{"class":57}," main",[40,471,118],{"class":61},[40,473,475],{"class":42,"line":474},40,[40,476,477],{"class":46},"    \u002F\u002F Собираем конвейер\n",[40,479,481,484,486,488,491,493,496,499,502,504,506,509,511,514,516,519,521,524,526,529,531,534,536,539,541,544],{"class":42,"line":480},41,[40,482,483],{"class":61},"    nums ",[40,485,93],{"class":53},[40,487,58],{"class":57},[40,489,490],{"class":61},"([]",[40,492,72],{"class":53},[40,494,495],{"class":61},"{",[40,497,498],{"class":409},"1",[40,500,501],{"class":61},", ",[40,503,410],{"class":409},[40,505,501],{"class":61},[40,507,508],{"class":409},"3",[40,510,501],{"class":61},[40,512,513],{"class":409},"4",[40,515,501],{"class":61},[40,517,518],{"class":409},"5",[40,520,501],{"class":61},[40,522,523],{"class":409},"6",[40,525,501],{"class":61},[40,527,528],{"class":409},"7",[40,530,501],{"class":61},[40,532,533],{"class":409},"8",[40,535,501],{"class":61},[40,537,538],{"class":409},"9",[40,540,501],{"class":61},[40,542,543],{"class":409},"10",[40,545,546],{"class":61},"})\n",[40,548,550,553,555,557],{"class":42,"line":549},42,[40,551,552],{"class":61},"    squared ",[40,554,93],{"class":53},[40,556,207],{"class":57},[40,558,559],{"class":61},"(nums)\n",[40,561,563,566,568,570],{"class":42,"line":562},43,[40,564,565],{"class":61},"    filtered ",[40,567,93],{"class":53},[40,569,330],{"class":57},[40,571,572],{"class":61},"(squared)\n",[40,574,576],{"class":42,"line":575},44,[40,577,193],{"emptyLinePlaceholder":192},[40,579,581,584,587,589,591],{"class":42,"line":580},45,[40,582,583],{"class":53},"    for",[40,585,586],{"class":61}," result ",[40,588,93],{"class":53},[40,590,144],{"class":53},[40,592,593],{"class":61}," filtered {\n",[40,595,597,600,603,606],{"class":42,"line":596},46,[40,598,599],{"class":61},"        fmt.",[40,601,602],{"class":57},"Println",[40,604,605],{"class":61},"(result) ",[40,607,608],{"class":46},"\u002F\u002F 4, 16, 36, 64, 100\n",[40,610,612],{"class":42,"line":611},47,[40,613,614],{"class":61},"    }\n",[40,616,618],{"class":42,"line":617},48,[40,619,186],{"class":61},[15,621,622,623,626],{},"Каждая стадия — отдельная функция, возвращающая канал. Стадии соединяются как unix-пайпы: ",[37,624,625],{},"cat file | grep pattern | wc -l",". Добавление новой стадии не ломает существующие.",[628,629,631],"h3",{"id":630},"pipeline-с-отменой-через-context","Pipeline с отменой через Context",[15,633,634],{},"Без контекста pipeline не остановить снаружи — горутины зависнут если потребитель перестанет читать:",[30,636,638],{"className":32,"code":637,"language":34,"meta":35,"style":35},"func generateCtx(ctx context.Context, nums []int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for _, n := range nums {\n            select {\n            case out \u003C- n:\n            case \u003C-ctx.Done(): \u002F\u002F прерываемся если контекст отменён\n                return\n            }\n        }\n    }()\n    return out\n}\n\nfunc squareCtx(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 main() {\n    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n    defer cancel()\n\n    nums := generateCtx(ctx, []int{1, 2, 3, 4, 5})\n    squared := squareCtx(ctx, nums)\n\n    for result := range squared {\n        fmt.Println(result)\n    }\n}\n",[37,639,640,677,693,701,709,721,728,741,760,765,769,773,777,783,787,791,824,840,848,856,868,874,888,901,905,909,913,917,923,927,931,939,968,979,983,1018,1029,1033,1046,1055,1059],{"__ignoreMap":35},[40,641,642,644,647,649,652,655,658,661,663,665,667,669,671,673,675],{"class":42,"line":43},[40,643,54],{"class":53},[40,645,646],{"class":57}," generateCtx",[40,648,62],{"class":61},[40,650,651],{"class":65},"ctx",[40,653,654],{"class":57}," context",[40,656,657],{"class":61},".",[40,659,660],{"class":57},"Context",[40,662,501],{"class":61},[40,664,66],{"class":65},[40,666,69],{"class":61},[40,668,72],{"class":53},[40,670,75],{"class":61},[40,672,78],{"class":53},[40,674,81],{"class":53},[40,676,84],{"class":61},[40,678,679,681,683,685,687,689,691],{"class":42,"line":50},[40,680,90],{"class":61},[40,682,93],{"class":53},[40,684,96],{"class":57},[40,686,62],{"class":61},[40,688,101],{"class":53},[40,690,81],{"class":53},[40,692,106],{"class":61},[40,694,695,697,699],{"class":42,"line":87},[40,696,112],{"class":53},[40,698,115],{"class":53},[40,700,118],{"class":61},[40,702,703,705,707],{"class":42,"line":109},[40,704,124],{"class":53},[40,706,127],{"class":57},[40,708,130],{"class":61},[40,710,711,713,715,717,719],{"class":42,"line":121},[40,712,136],{"class":53},[40,714,139],{"class":61},[40,716,93],{"class":53},[40,718,144],{"class":53},[40,720,147],{"class":61},[40,722,723,726],{"class":42,"line":133},[40,724,725],{"class":53},"            select",[40,727,84],{"class":61},[40,729,730,733,736,738],{"class":42,"line":150},[40,731,732],{"class":53},"            case",[40,734,735],{"class":61}," out ",[40,737,156],{"class":53},[40,739,740],{"class":61}," n:\n",[40,742,743,745,748,751,754,757],{"class":42,"line":162},[40,744,732],{"class":53},[40,746,747],{"class":53}," \u003C-",[40,749,750],{"class":61},"ctx.",[40,752,753],{"class":57},"Done",[40,755,756],{"class":61},"(): ",[40,758,759],{"class":46},"\u002F\u002F прерываемся если контекст отменён\n",[40,761,762],{"class":42,"line":168},[40,763,764],{"class":53},"                return\n",[40,766,767],{"class":42,"line":174},[40,768,434],{"class":61},[40,770,771],{"class":42,"line":183},[40,772,165],{"class":61},[40,774,775],{"class":42,"line":189},[40,776,171],{"class":61},[40,778,779,781],{"class":42,"line":196},[40,780,177],{"class":53},[40,782,180],{"class":61},[40,784,785],{"class":42,"line":202},[40,786,186],{"class":61},[40,788,789],{"class":42,"line":228},[40,790,193],{"emptyLinePlaceholder":192},[40,792,793,795,798,800,802,804,806,808,810,812,814,816,818,820,822],{"class":42,"line":245},[40,794,54],{"class":53},[40,796,797],{"class":57}," squareCtx",[40,799,62],{"class":61},[40,801,651],{"class":65},[40,803,654],{"class":57},[40,805,657],{"class":61},[40,807,660],{"class":57},[40,809,501],{"class":61},[40,811,212],{"class":65},[40,813,215],{"class":53},[40,815,81],{"class":53},[40,817,75],{"class":61},[40,819,78],{"class":53},[40,821,81],{"class":53},[40,823,84],{"class":61},[40,825,826,828,830,832,834,836,838],{"class":42,"line":254},[40,827,90],{"class":61},[40,829,93],{"class":53},[40,831,96],{"class":57},[40,833,62],{"class":61},[40,835,101],{"class":53},[40,837,81],{"class":53},[40,839,106],{"class":61},[40,841,842,844,846],{"class":42,"line":263},[40,843,112],{"class":53},[40,845,115],{"class":53},[40,847,118],{"class":61},[40,849,850,852,854],{"class":42,"line":278},[40,851,124],{"class":53},[40,853,127],{"class":57},[40,855,130],{"class":61},[40,857,858,860,862,864,866],{"class":42,"line":292},[40,859,136],{"class":53},[40,861,268],{"class":61},[40,863,93],{"class":53},[40,865,144],{"class":53},[40,867,275],{"class":61},[40,869,870,872],{"class":42,"line":297},[40,871,725],{"class":53},[40,873,84],{"class":61},[40,875,876,878,880,882,884,886],{"class":42,"line":302},[40,877,732],{"class":53},[40,879,735],{"class":61},[40,881,156],{"class":53},[40,883,268],{"class":61},[40,885,287],{"class":53},[40,887,740],{"class":61},[40,889,890,892,894,896,898],{"class":42,"line":309},[40,891,732],{"class":53},[40,893,747],{"class":53},[40,895,750],{"class":61},[40,897,753],{"class":57},[40,899,900],{"class":61},"():\n",[40,902,903],{"class":42,"line":314},[40,904,764],{"class":53},[40,906,907],{"class":42,"line":319},[40,908,434],{"class":61},[40,910,911],{"class":42,"line":325},[40,912,165],{"class":61},[40,914,915],{"class":42,"line":349},[40,916,171],{"class":61},[40,918,919,921],{"class":42,"line":366},[40,920,177],{"class":53},[40,922,180],{"class":61},[40,924,925],{"class":42,"line":375},[40,926,186],{"class":61},[40,928,929],{"class":42,"line":384},[40,930,193],{"emptyLinePlaceholder":192},[40,932,933,935,937],{"class":42,"line":397},[40,934,54],{"class":53},[40,936,469],{"class":57},[40,938,118],{"class":61},[40,940,941,944,946,949,952,955,958,961,963,965],{"class":42,"line":421},[40,942,943],{"class":61},"    ctx, cancel ",[40,945,93],{"class":53},[40,947,948],{"class":61}," context.",[40,950,951],{"class":57},"WithTimeout",[40,953,954],{"class":61},"(context.",[40,956,957],{"class":57},"Background",[40,959,960],{"class":61},"(), ",[40,962,410],{"class":409},[40,964,287],{"class":53},[40,966,967],{"class":61},"time.Second)\n",[40,969,970,973,976],{"class":42,"line":431},[40,971,972],{"class":53},"    defer",[40,974,975],{"class":57}," cancel",[40,977,978],{"class":61},"()\n",[40,980,981],{"class":42,"line":437},[40,982,193],{"emptyLinePlaceholder":192},[40,984,985,987,989,991,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016],{"class":42,"line":442},[40,986,483],{"class":61},[40,988,93],{"class":53},[40,990,646],{"class":57},[40,992,993],{"class":61},"(ctx, []",[40,995,72],{"class":53},[40,997,495],{"class":61},[40,999,498],{"class":409},[40,1001,501],{"class":61},[40,1003,410],{"class":409},[40,1005,501],{"class":61},[40,1007,508],{"class":409},[40,1009,501],{"class":61},[40,1011,513],{"class":409},[40,1013,501],{"class":61},[40,1015,518],{"class":409},[40,1017,546],{"class":61},[40,1019,1020,1022,1024,1026],{"class":42,"line":447},[40,1021,552],{"class":61},[40,1023,93],{"class":53},[40,1025,797],{"class":57},[40,1027,1028],{"class":61},"(ctx, nums)\n",[40,1030,1031],{"class":42,"line":454},[40,1032,193],{"emptyLinePlaceholder":192},[40,1034,1035,1037,1039,1041,1043],{"class":42,"line":459},[40,1036,583],{"class":53},[40,1038,586],{"class":61},[40,1040,93],{"class":53},[40,1042,144],{"class":53},[40,1044,1045],{"class":61}," squared {\n",[40,1047,1048,1050,1052],{"class":42,"line":464},[40,1049,599],{"class":61},[40,1051,602],{"class":57},[40,1053,1054],{"class":61},"(result)\n",[40,1056,1057],{"class":42,"line":474},[40,1058,614],{"class":61},[40,1060,1061],{"class":42,"line":480},[40,1062,186],{"class":61},[15,1064,1065],{},"Правило: каждая стадия pipeline должна поддерживать отмену через context — иначе при отмене горутины зависнут, заблокированные на отправке в канал, которого никто не читает.",[19,1067],{},[22,1069,1071],{"id":1070},"fan-out-fan-in","Fan-out \u002F Fan-in",[15,1073,1074,1078,1079,1082],{},[1075,1076,1077],"strong",{},"Fan-out"," — распределение работы между несколькими горутинами из одного источника. ",[1075,1080,1081],{},"Fan-in"," — объединение результатов из нескольких каналов в один. Вместе они реализуют параллельную обработку с агрегацией результатов:",[30,1084,1086],{"className":32,"code":1085,"language":34,"meta":35,"style":35},"\u002F\u002F Fan-out: запускаем N воркеров, каждый читает из одного канала\nfunc fanOut(ctx context.Context, in \u003C-chan Job, workers int) []\u003C-chan Result {\n    channels := make([]\u003C-chan Result, workers)\n    for i := 0; i \u003C workers; i++ {\n        channels[i] = worker(ctx, in)\n    }\n    return channels\n}\n\nfunc worker(ctx context.Context, in \u003C-chan Job) \u003C-chan Result {\n    out := make(chan Result)\n    go func() {\n        defer close(out)\n        for job := range in {\n            select {\n            case \u003C-ctx.Done():\n                return\n            case out \u003C- process(job):\n            }\n        }\n    }()\n    return out\n}\n\n\u002F\u002F Fan-in: объединяем несколько каналов в один\nfunc fanIn(ctx context.Context, channels ...\u003C-chan Result) \u003C-chan Result {\n    merged := make(chan Result)\n    var wg sync.WaitGroup\n\n    \u002F\u002F Для каждого входного канала запускаем горутину-форвардер\n    forward := func(ch \u003C-chan Result) {\n        defer wg.Done()\n        for result := range ch {\n            select {\n            case merged \u003C- result:\n            case \u003C-ctx.Done():\n                return\n            }\n        }\n    }\n\n    wg.Add(len(channels))\n    for _, ch := range channels {\n        go forward(ch)\n    }\n\n    \u002F\u002F Закрываем merged когда все форвардеры завершились\n    go func() {\n        wg.Wait()\n        close(merged)\n    }()\n\n    return merged\n}\n\n\u002F\u002F Использование\nfunc main() {\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n    defer cancel()\n\n    jobs := generateJobs(ctx, 1000)\n\n    \u002F\u002F Fan-out: 5 воркеров параллельно обрабатывают задачи\n    workerChannels := fanOut(ctx, jobs, 5)\n\n    \u002F\u002F Fan-in: собираем все результаты в один канал\n    results := fanIn(ctx, workerChannels...)\n\n    for result := range results {\n        fmt.Println(result)\n    }\n}\n",[37,1087,1088,1093,1136,1154,1179,1193,1197,1204,1208,1212,1244,1260,1268,1276,1289,1295,1307,1311,1325,1329,1333,1337,1343,1347,1351,1356,1391,1408,1424,1428,1433,1454,1465,1478,1484,1496,1508,1512,1516,1520,1524,1528,1544,1558,1569,1573,1577,1582,1590,1601,1610,1615,1620,1628,1633,1638,1644,1653,1676,1685,1690,1709,1714,1720,1737,1742,1748,1766,1771,1785,1794,1799],{"__ignoreMap":35},[40,1089,1090],{"class":42,"line":43},[40,1091,1092],{"class":46},"\u002F\u002F Fan-out: запускаем N воркеров, каждый читает из одного канала\n",[40,1094,1095,1097,1100,1102,1104,1106,1108,1110,1112,1114,1116,1119,1121,1124,1126,1129,1131,1134],{"class":42,"line":50},[40,1096,54],{"class":53},[40,1098,1099],{"class":57}," fanOut",[40,1101,62],{"class":61},[40,1103,651],{"class":65},[40,1105,654],{"class":57},[40,1107,657],{"class":61},[40,1109,660],{"class":57},[40,1111,501],{"class":61},[40,1113,212],{"class":65},[40,1115,215],{"class":53},[40,1117,1118],{"class":57}," Job",[40,1120,501],{"class":61},[40,1122,1123],{"class":65},"workers",[40,1125,81],{"class":53},[40,1127,1128],{"class":61},") []",[40,1130,78],{"class":53},[40,1132,1133],{"class":57}," Result",[40,1135,84],{"class":61},[40,1137,1138,1141,1143,1145,1147,1149,1151],{"class":42,"line":87},[40,1139,1140],{"class":61},"    channels ",[40,1142,93],{"class":53},[40,1144,96],{"class":57},[40,1146,490],{"class":61},[40,1148,78],{"class":53},[40,1150,1133],{"class":57},[40,1152,1153],{"class":61},", workers)\n",[40,1155,1156,1158,1161,1163,1165,1168,1171,1174,1177],{"class":42,"line":109},[40,1157,583],{"class":53},[40,1159,1160],{"class":61}," i ",[40,1162,93],{"class":53},[40,1164,416],{"class":409},[40,1166,1167],{"class":61},"; i ",[40,1169,1170],{"class":53},"\u003C",[40,1172,1173],{"class":61}," workers; i",[40,1175,1176],{"class":53},"++",[40,1178,84],{"class":61},[40,1180,1181,1184,1187,1190],{"class":42,"line":121},[40,1182,1183],{"class":61},"        channels[i] ",[40,1185,1186],{"class":53},"=",[40,1188,1189],{"class":57}," worker",[40,1191,1192],{"class":61},"(ctx, in)\n",[40,1194,1195],{"class":42,"line":133},[40,1196,614],{"class":61},[40,1198,1199,1201],{"class":42,"line":150},[40,1200,177],{"class":53},[40,1202,1203],{"class":61}," channels\n",[40,1205,1206],{"class":42,"line":162},[40,1207,186],{"class":61},[40,1209,1210],{"class":42,"line":168},[40,1211,193],{"emptyLinePlaceholder":192},[40,1213,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242],{"class":42,"line":174},[40,1215,54],{"class":53},[40,1217,1189],{"class":57},[40,1219,62],{"class":61},[40,1221,651],{"class":65},[40,1223,654],{"class":57},[40,1225,657],{"class":61},[40,1227,660],{"class":57},[40,1229,501],{"class":61},[40,1231,212],{"class":65},[40,1233,215],{"class":53},[40,1235,1118],{"class":57},[40,1237,75],{"class":61},[40,1239,78],{"class":53},[40,1241,1133],{"class":57},[40,1243,84],{"class":61},[40,1245,1246,1248,1250,1252,1254,1256,1258],{"class":42,"line":183},[40,1247,90],{"class":61},[40,1249,93],{"class":53},[40,1251,96],{"class":57},[40,1253,62],{"class":61},[40,1255,101],{"class":53},[40,1257,1133],{"class":57},[40,1259,106],{"class":61},[40,1261,1262,1264,1266],{"class":42,"line":189},[40,1263,112],{"class":53},[40,1265,115],{"class":53},[40,1267,118],{"class":61},[40,1269,1270,1272,1274],{"class":42,"line":196},[40,1271,124],{"class":53},[40,1273,127],{"class":57},[40,1275,130],{"class":61},[40,1277,1278,1280,1283,1285,1287],{"class":42,"line":202},[40,1279,136],{"class":53},[40,1281,1282],{"class":61}," job ",[40,1284,93],{"class":53},[40,1286,144],{"class":53},[40,1288,275],{"class":61},[40,1290,1291,1293],{"class":42,"line":228},[40,1292,725],{"class":53},[40,1294,84],{"class":61},[40,1296,1297,1299,1301,1303,1305],{"class":42,"line":245},[40,1298,732],{"class":53},[40,1300,747],{"class":53},[40,1302,750],{"class":61},[40,1304,753],{"class":57},[40,1306,900],{"class":61},[40,1308,1309],{"class":42,"line":254},[40,1310,764],{"class":53},[40,1312,1313,1315,1317,1319,1322],{"class":42,"line":263},[40,1314,732],{"class":53},[40,1316,735],{"class":61},[40,1318,156],{"class":53},[40,1320,1321],{"class":57}," process",[40,1323,1324],{"class":61},"(job):\n",[40,1326,1327],{"class":42,"line":278},[40,1328,434],{"class":61},[40,1330,1331],{"class":42,"line":292},[40,1332,165],{"class":61},[40,1334,1335],{"class":42,"line":297},[40,1336,171],{"class":61},[40,1338,1339,1341],{"class":42,"line":302},[40,1340,177],{"class":53},[40,1342,180],{"class":61},[40,1344,1345],{"class":42,"line":309},[40,1346,186],{"class":61},[40,1348,1349],{"class":42,"line":314},[40,1350,193],{"emptyLinePlaceholder":192},[40,1352,1353],{"class":42,"line":319},[40,1354,1355],{"class":46},"\u002F\u002F Fan-in: объединяем несколько каналов в один\n",[40,1357,1358,1360,1363,1365,1367,1369,1371,1373,1375,1378,1381,1383,1385,1387,1389],{"class":42,"line":325},[40,1359,54],{"class":53},[40,1361,1362],{"class":57}," fanIn",[40,1364,62],{"class":61},[40,1366,651],{"class":65},[40,1368,654],{"class":57},[40,1370,657],{"class":61},[40,1372,660],{"class":57},[40,1374,501],{"class":61},[40,1376,1377],{"class":65},"channels",[40,1379,1380],{"class":53}," ...\u003C-chan",[40,1382,1133],{"class":57},[40,1384,75],{"class":61},[40,1386,78],{"class":53},[40,1388,1133],{"class":57},[40,1390,84],{"class":61},[40,1392,1393,1396,1398,1400,1402,1404,1406],{"class":42,"line":349},[40,1394,1395],{"class":61},"    merged ",[40,1397,93],{"class":53},[40,1399,96],{"class":57},[40,1401,62],{"class":61},[40,1403,101],{"class":53},[40,1405,1133],{"class":57},[40,1407,106],{"class":61},[40,1409,1410,1413,1416,1419,1421],{"class":42,"line":366},[40,1411,1412],{"class":53},"    var",[40,1414,1415],{"class":61}," wg ",[40,1417,1418],{"class":57},"sync",[40,1420,657],{"class":61},[40,1422,1423],{"class":57},"WaitGroup\n",[40,1425,1426],{"class":42,"line":375},[40,1427,193],{"emptyLinePlaceholder":192},[40,1429,1430],{"class":42,"line":384},[40,1431,1432],{"class":46},"    \u002F\u002F Для каждого входного канала запускаем горутину-форвардер\n",[40,1434,1435,1438,1440,1442,1444,1447,1449,1451],{"class":42,"line":397},[40,1436,1437],{"class":61},"    forward ",[40,1439,93],{"class":53},[40,1441,115],{"class":53},[40,1443,62],{"class":61},[40,1445,1446],{"class":65},"ch",[40,1448,215],{"class":53},[40,1450,1133],{"class":57},[40,1452,1453],{"class":61},") {\n",[40,1455,1456,1458,1461,1463],{"class":42,"line":421},[40,1457,124],{"class":53},[40,1459,1460],{"class":61}," wg.",[40,1462,753],{"class":57},[40,1464,978],{"class":61},[40,1466,1467,1469,1471,1473,1475],{"class":42,"line":431},[40,1468,136],{"class":53},[40,1470,586],{"class":61},[40,1472,93],{"class":53},[40,1474,144],{"class":53},[40,1476,1477],{"class":61}," ch {\n",[40,1479,1480,1482],{"class":42,"line":437},[40,1481,725],{"class":53},[40,1483,84],{"class":61},[40,1485,1486,1488,1491,1493],{"class":42,"line":442},[40,1487,732],{"class":53},[40,1489,1490],{"class":61}," merged ",[40,1492,156],{"class":53},[40,1494,1495],{"class":61}," result:\n",[40,1497,1498,1500,1502,1504,1506],{"class":42,"line":447},[40,1499,732],{"class":53},[40,1501,747],{"class":53},[40,1503,750],{"class":61},[40,1505,753],{"class":57},[40,1507,900],{"class":61},[40,1509,1510],{"class":42,"line":454},[40,1511,764],{"class":53},[40,1513,1514],{"class":42,"line":459},[40,1515,434],{"class":61},[40,1517,1518],{"class":42,"line":464},[40,1519,165],{"class":61},[40,1521,1522],{"class":42,"line":474},[40,1523,614],{"class":61},[40,1525,1526],{"class":42,"line":480},[40,1527,193],{"emptyLinePlaceholder":192},[40,1529,1530,1533,1536,1538,1541],{"class":42,"line":549},[40,1531,1532],{"class":61},"    wg.",[40,1534,1535],{"class":57},"Add",[40,1537,62],{"class":61},[40,1539,1540],{"class":57},"len",[40,1542,1543],{"class":61},"(channels))\n",[40,1545,1546,1548,1551,1553,1555],{"class":42,"line":562},[40,1547,583],{"class":53},[40,1549,1550],{"class":61}," _, ch ",[40,1552,93],{"class":53},[40,1554,144],{"class":53},[40,1556,1557],{"class":61}," channels {\n",[40,1559,1560,1563,1566],{"class":42,"line":575},[40,1561,1562],{"class":53},"        go",[40,1564,1565],{"class":57}," forward",[40,1567,1568],{"class":61},"(ch)\n",[40,1570,1571],{"class":42,"line":580},[40,1572,614],{"class":61},[40,1574,1575],{"class":42,"line":596},[40,1576,193],{"emptyLinePlaceholder":192},[40,1578,1579],{"class":42,"line":611},[40,1580,1581],{"class":46},"    \u002F\u002F Закрываем merged когда все форвардеры завершились\n",[40,1583,1584,1586,1588],{"class":42,"line":617},[40,1585,112],{"class":53},[40,1587,115],{"class":53},[40,1589,118],{"class":61},[40,1591,1593,1596,1599],{"class":42,"line":1592},49,[40,1594,1595],{"class":61},"        wg.",[40,1597,1598],{"class":57},"Wait",[40,1600,978],{"class":61},[40,1602,1604,1607],{"class":42,"line":1603},50,[40,1605,1606],{"class":57},"        close",[40,1608,1609],{"class":61},"(merged)\n",[40,1611,1613],{"class":42,"line":1612},51,[40,1614,171],{"class":61},[40,1616,1618],{"class":42,"line":1617},52,[40,1619,193],{"emptyLinePlaceholder":192},[40,1621,1623,1625],{"class":42,"line":1622},53,[40,1624,177],{"class":53},[40,1626,1627],{"class":61}," merged\n",[40,1629,1631],{"class":42,"line":1630},54,[40,1632,186],{"class":61},[40,1634,1636],{"class":42,"line":1635},55,[40,1637,193],{"emptyLinePlaceholder":192},[40,1639,1641],{"class":42,"line":1640},56,[40,1642,1643],{"class":46},"\u002F\u002F Использование\n",[40,1645,1647,1649,1651],{"class":42,"line":1646},57,[40,1648,54],{"class":53},[40,1650,469],{"class":57},[40,1652,118],{"class":61},[40,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674],{"class":42,"line":1655},58,[40,1657,943],{"class":61},[40,1659,93],{"class":53},[40,1661,948],{"class":61},[40,1663,951],{"class":57},[40,1665,954],{"class":61},[40,1667,957],{"class":57},[40,1669,960],{"class":61},[40,1671,543],{"class":409},[40,1673,287],{"class":53},[40,1675,967],{"class":61},[40,1677,1679,1681,1683],{"class":42,"line":1678},59,[40,1680,972],{"class":53},[40,1682,975],{"class":57},[40,1684,978],{"class":61},[40,1686,1688],{"class":42,"line":1687},60,[40,1689,193],{"emptyLinePlaceholder":192},[40,1691,1693,1696,1698,1701,1704,1707],{"class":42,"line":1692},61,[40,1694,1695],{"class":61},"    jobs ",[40,1697,93],{"class":53},[40,1699,1700],{"class":57}," generateJobs",[40,1702,1703],{"class":61},"(ctx, ",[40,1705,1706],{"class":409},"1000",[40,1708,106],{"class":61},[40,1710,1712],{"class":42,"line":1711},62,[40,1713,193],{"emptyLinePlaceholder":192},[40,1715,1717],{"class":42,"line":1716},63,[40,1718,1719],{"class":46},"    \u002F\u002F Fan-out: 5 воркеров параллельно обрабатывают задачи\n",[40,1721,1723,1726,1728,1730,1733,1735],{"class":42,"line":1722},64,[40,1724,1725],{"class":61},"    workerChannels ",[40,1727,93],{"class":53},[40,1729,1099],{"class":57},[40,1731,1732],{"class":61},"(ctx, jobs, ",[40,1734,518],{"class":409},[40,1736,106],{"class":61},[40,1738,1740],{"class":42,"line":1739},65,[40,1741,193],{"emptyLinePlaceholder":192},[40,1743,1745],{"class":42,"line":1744},66,[40,1746,1747],{"class":46},"    \u002F\u002F Fan-in: собираем все результаты в один канал\n",[40,1749,1751,1754,1756,1758,1761,1764],{"class":42,"line":1750},67,[40,1752,1753],{"class":61},"    results ",[40,1755,93],{"class":53},[40,1757,1362],{"class":57},[40,1759,1760],{"class":61},"(ctx, workerChannels",[40,1762,1763],{"class":53},"...",[40,1765,106],{"class":61},[40,1767,1769],{"class":42,"line":1768},68,[40,1770,193],{"emptyLinePlaceholder":192},[40,1772,1774,1776,1778,1780,1782],{"class":42,"line":1773},69,[40,1775,583],{"class":53},[40,1777,586],{"class":61},[40,1779,93],{"class":53},[40,1781,144],{"class":53},[40,1783,1784],{"class":61}," results {\n",[40,1786,1788,1790,1792],{"class":42,"line":1787},70,[40,1789,599],{"class":61},[40,1791,602],{"class":57},[40,1793,1054],{"class":61},[40,1795,1797],{"class":42,"line":1796},71,[40,1798,614],{"class":61},[40,1800,1802],{"class":42,"line":1801},72,[40,1803,186],{"class":61},[15,1805,1806],{},"Fan-out\u002Ffan-in эффективен когда задачи независимы и каждая занимает примерно одинаковое время. Если время сильно варьируется — лучше worker pool.",[19,1808],{},[22,1810,1812],{"id":1811},"worker-pool-пул-воркеров","Worker Pool — пул воркеров",[15,1814,1815],{},"Worker pool — фиксированное количество горутин, обрабатывающих очередь задач. В отличие от fan-out, количество горутин не зависит от количества задач:",[30,1817,1819],{"className":32,"code":1818,"language":34,"meta":35,"style":35},"type Job struct {\n    ID   int\n    Data string\n}\n\ntype Result struct {\n    JobID  int\n    Output string\n    Err    error\n}\n\nfunc workerPool(\n    ctx context.Context,\n    numWorkers int,\n    jobs \u003C-chan Job,\n) \u003C-chan Result {\n    results := make(chan Result, numWorkers)\n\n    var wg sync.WaitGroup\n    for i := 0; i \u003C numWorkers; i++ {\n        wg.Add(1)\n        go func(workerID int) {\n            defer wg.Done()\n            for {\n                select {\n                case job, ok := \u003C-jobs:\n                    if !ok {\n                        return \u002F\u002F канал закрыт — завершаем воркер\n                    }\n                    output, err := processJob(job)\n                    results \u003C- Result{\n                        JobID:  job.ID,\n                        Output: output,\n                        Err:    err,\n                    }\n                case \u003C-ctx.Done():\n                    return\n                }\n            }\n        }(i)\n    }\n\n    \u002F\u002F Закрываем results когда все воркеры завершились\n    go func() {\n        wg.Wait()\n        close(results)\n    }()\n\n    return results\n}\n\nfunc main() {\n    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n    defer cancel()\n\n    \u002F\u002F Создаём задачи\n    jobs := make(chan Job, 100)\n    go func() {\n        defer close(jobs)\n        for i := 0; i \u003C 1000; i++ {\n            jobs \u003C- Job{ID: i, Data: fmt.Sprintf(\"data-%d\", i)}\\n        }\n    }()\n\n    \u002F\u002F Запускаем пул из 10 воркеров\n    results := workerPool(ctx, 10, jobs)\n\n    \u002F\u002F Собираем результаты\n    var errors []error\n    for result := range results {\n        if result.Err != nil {\n            errors = append(errors, result.Err)\n            continue\n        }\n        fmt.Println(result.Output)\n    }\n}\n",[37,1820,1821,1833,1841,1849,1853,1857,1867,1874,1881,1889,1893,1897,1907,1921,1930,1941,1951,1968,1972,1984,2005,2017,2032,2043,2050,2057,2072,2083,2091,2096,2109,2121,2126,2131,2136,2140,2152,2157,2162,2166,2171,2175,2179,2184,2192,2200,2207,2211,2215,2222,2226,2230,2238,2261,2269,2273,2278,2299,2307,2316,2340,2370,2374,2378,2383,2398,2402,2407,2416,2428,2444,2457,2462,2467,2477,2482],{"__ignoreMap":35},[40,1822,1823,1826,1828,1831],{"class":42,"line":43},[40,1824,1825],{"class":53},"type",[40,1827,1118],{"class":57},[40,1829,1830],{"class":53}," struct",[40,1832,84],{"class":61},[40,1834,1835,1838],{"class":42,"line":50},[40,1836,1837],{"class":61},"    ID   ",[40,1839,1840],{"class":53},"int\n",[40,1842,1843,1846],{"class":42,"line":87},[40,1844,1845],{"class":61},"    Data ",[40,1847,1848],{"class":53},"string\n",[40,1850,1851],{"class":42,"line":109},[40,1852,186],{"class":61},[40,1854,1855],{"class":42,"line":121},[40,1856,193],{"emptyLinePlaceholder":192},[40,1858,1859,1861,1863,1865],{"class":42,"line":133},[40,1860,1825],{"class":53},[40,1862,1133],{"class":57},[40,1864,1830],{"class":53},[40,1866,84],{"class":61},[40,1868,1869,1872],{"class":42,"line":150},[40,1870,1871],{"class":61},"    JobID  ",[40,1873,1840],{"class":53},[40,1875,1876,1879],{"class":42,"line":162},[40,1877,1878],{"class":61},"    Output ",[40,1880,1848],{"class":53},[40,1882,1883,1886],{"class":42,"line":168},[40,1884,1885],{"class":61},"    Err    ",[40,1887,1888],{"class":53},"error\n",[40,1890,1891],{"class":42,"line":174},[40,1892,186],{"class":61},[40,1894,1895],{"class":42,"line":183},[40,1896,193],{"emptyLinePlaceholder":192},[40,1898,1899,1901,1904],{"class":42,"line":189},[40,1900,54],{"class":53},[40,1902,1903],{"class":57}," workerPool",[40,1905,1906],{"class":61},"(\n",[40,1908,1909,1912,1914,1916,1918],{"class":42,"line":196},[40,1910,1911],{"class":65},"    ctx",[40,1913,654],{"class":57},[40,1915,657],{"class":61},[40,1917,660],{"class":57},[40,1919,1920],{"class":61},",\n",[40,1922,1923,1926,1928],{"class":42,"line":202},[40,1924,1925],{"class":65},"    numWorkers",[40,1927,81],{"class":53},[40,1929,1920],{"class":61},[40,1931,1932,1935,1937,1939],{"class":42,"line":228},[40,1933,1934],{"class":65},"    jobs",[40,1936,215],{"class":53},[40,1938,1118],{"class":57},[40,1940,1920],{"class":61},[40,1942,1943,1945,1947,1949],{"class":42,"line":245},[40,1944,75],{"class":61},[40,1946,78],{"class":53},[40,1948,1133],{"class":57},[40,1950,84],{"class":61},[40,1952,1953,1955,1957,1959,1961,1963,1965],{"class":42,"line":254},[40,1954,1753],{"class":61},[40,1956,93],{"class":53},[40,1958,96],{"class":57},[40,1960,62],{"class":61},[40,1962,101],{"class":53},[40,1964,1133],{"class":57},[40,1966,1967],{"class":61},", numWorkers)\n",[40,1969,1970],{"class":42,"line":263},[40,1971,193],{"emptyLinePlaceholder":192},[40,1973,1974,1976,1978,1980,1982],{"class":42,"line":278},[40,1975,1412],{"class":53},[40,1977,1415],{"class":61},[40,1979,1418],{"class":57},[40,1981,657],{"class":61},[40,1983,1423],{"class":57},[40,1985,1986,1988,1990,1992,1994,1996,1998,2001,2003],{"class":42,"line":292},[40,1987,583],{"class":53},[40,1989,1160],{"class":61},[40,1991,93],{"class":53},[40,1993,416],{"class":409},[40,1995,1167],{"class":61},[40,1997,1170],{"class":53},[40,1999,2000],{"class":61}," numWorkers; i",[40,2002,1176],{"class":53},[40,2004,84],{"class":61},[40,2006,2007,2009,2011,2013,2015],{"class":42,"line":297},[40,2008,1595],{"class":61},[40,2010,1535],{"class":57},[40,2012,62],{"class":61},[40,2014,498],{"class":409},[40,2016,106],{"class":61},[40,2018,2019,2021,2023,2025,2028,2030],{"class":42,"line":302},[40,2020,1562],{"class":53},[40,2022,115],{"class":53},[40,2024,62],{"class":61},[40,2026,2027],{"class":65},"workerID",[40,2029,81],{"class":53},[40,2031,1453],{"class":61},[40,2033,2034,2037,2039,2041],{"class":42,"line":309},[40,2035,2036],{"class":53},"            defer",[40,2038,1460],{"class":61},[40,2040,753],{"class":57},[40,2042,978],{"class":61},[40,2044,2045,2048],{"class":42,"line":314},[40,2046,2047],{"class":53},"            for",[40,2049,84],{"class":61},[40,2051,2052,2055],{"class":42,"line":319},[40,2053,2054],{"class":53},"                select",[40,2056,84],{"class":61},[40,2058,2059,2062,2065,2067,2069],{"class":42,"line":325},[40,2060,2061],{"class":53},"                case",[40,2063,2064],{"class":61}," job, ok ",[40,2066,93],{"class":53},[40,2068,747],{"class":53},[40,2070,2071],{"class":61},"jobs:\n",[40,2073,2074,2077,2080],{"class":42,"line":349},[40,2075,2076],{"class":53},"                    if",[40,2078,2079],{"class":53}," !",[40,2081,2082],{"class":61},"ok {\n",[40,2084,2085,2088],{"class":42,"line":366},[40,2086,2087],{"class":53},"                        return",[40,2089,2090],{"class":46}," \u002F\u002F канал закрыт — завершаем воркер\n",[40,2092,2093],{"class":42,"line":375},[40,2094,2095],{"class":61},"                    }\n",[40,2097,2098,2101,2103,2106],{"class":42,"line":384},[40,2099,2100],{"class":61},"                    output, err ",[40,2102,93],{"class":53},[40,2104,2105],{"class":57}," processJob",[40,2107,2108],{"class":61},"(job)\n",[40,2110,2111,2114,2116,2118],{"class":42,"line":397},[40,2112,2113],{"class":61},"                    results ",[40,2115,156],{"class":53},[40,2117,1133],{"class":57},[40,2119,2120],{"class":61},"{\n",[40,2122,2123],{"class":42,"line":421},[40,2124,2125],{"class":61},"                        JobID:  job.ID,\n",[40,2127,2128],{"class":42,"line":431},[40,2129,2130],{"class":61},"                        Output: output,\n",[40,2132,2133],{"class":42,"line":437},[40,2134,2135],{"class":61},"                        Err:    err,\n",[40,2137,2138],{"class":42,"line":442},[40,2139,2095],{"class":61},[40,2141,2142,2144,2146,2148,2150],{"class":42,"line":447},[40,2143,2061],{"class":53},[40,2145,747],{"class":53},[40,2147,750],{"class":61},[40,2149,753],{"class":57},[40,2151,900],{"class":61},[40,2153,2154],{"class":42,"line":454},[40,2155,2156],{"class":53},"                    return\n",[40,2158,2159],{"class":42,"line":459},[40,2160,2161],{"class":61},"                }\n",[40,2163,2164],{"class":42,"line":464},[40,2165,434],{"class":61},[40,2167,2168],{"class":42,"line":474},[40,2169,2170],{"class":61},"        }(i)\n",[40,2172,2173],{"class":42,"line":480},[40,2174,614],{"class":61},[40,2176,2177],{"class":42,"line":549},[40,2178,193],{"emptyLinePlaceholder":192},[40,2180,2181],{"class":42,"line":562},[40,2182,2183],{"class":46},"    \u002F\u002F Закрываем results когда все воркеры завершились\n",[40,2185,2186,2188,2190],{"class":42,"line":575},[40,2187,112],{"class":53},[40,2189,115],{"class":53},[40,2191,118],{"class":61},[40,2193,2194,2196,2198],{"class":42,"line":580},[40,2195,1595],{"class":61},[40,2197,1598],{"class":57},[40,2199,978],{"class":61},[40,2201,2202,2204],{"class":42,"line":596},[40,2203,1606],{"class":57},[40,2205,2206],{"class":61},"(results)\n",[40,2208,2209],{"class":42,"line":611},[40,2210,171],{"class":61},[40,2212,2213],{"class":42,"line":617},[40,2214,193],{"emptyLinePlaceholder":192},[40,2216,2217,2219],{"class":42,"line":1592},[40,2218,177],{"class":53},[40,2220,2221],{"class":61}," results\n",[40,2223,2224],{"class":42,"line":1603},[40,2225,186],{"class":61},[40,2227,2228],{"class":42,"line":1612},[40,2229,193],{"emptyLinePlaceholder":192},[40,2231,2232,2234,2236],{"class":42,"line":1617},[40,2233,54],{"class":53},[40,2235,469],{"class":57},[40,2237,118],{"class":61},[40,2239,2240,2242,2244,2246,2248,2250,2252,2254,2257,2259],{"class":42,"line":1622},[40,2241,943],{"class":61},[40,2243,93],{"class":53},[40,2245,948],{"class":61},[40,2247,951],{"class":57},[40,2249,954],{"class":61},[40,2251,957],{"class":57},[40,2253,960],{"class":61},[40,2255,2256],{"class":409},"30",[40,2258,287],{"class":53},[40,2260,967],{"class":61},[40,2262,2263,2265,2267],{"class":42,"line":1630},[40,2264,972],{"class":53},[40,2266,975],{"class":57},[40,2268,978],{"class":61},[40,2270,2271],{"class":42,"line":1635},[40,2272,193],{"emptyLinePlaceholder":192},[40,2274,2275],{"class":42,"line":1640},[40,2276,2277],{"class":46},"    \u002F\u002F Создаём задачи\n",[40,2279,2280,2282,2284,2286,2288,2290,2292,2294,2297],{"class":42,"line":1646},[40,2281,1695],{"class":61},[40,2283,93],{"class":53},[40,2285,96],{"class":57},[40,2287,62],{"class":61},[40,2289,101],{"class":53},[40,2291,1118],{"class":57},[40,2293,501],{"class":61},[40,2295,2296],{"class":409},"100",[40,2298,106],{"class":61},[40,2300,2301,2303,2305],{"class":42,"line":1655},[40,2302,112],{"class":53},[40,2304,115],{"class":53},[40,2306,118],{"class":61},[40,2308,2309,2311,2313],{"class":42,"line":1678},[40,2310,124],{"class":53},[40,2312,127],{"class":57},[40,2314,2315],{"class":61},"(jobs)\n",[40,2317,2318,2320,2322,2324,2326,2328,2330,2333,2336,2338],{"class":42,"line":1687},[40,2319,136],{"class":53},[40,2321,1160],{"class":61},[40,2323,93],{"class":53},[40,2325,416],{"class":409},[40,2327,1167],{"class":61},[40,2329,1170],{"class":53},[40,2331,2332],{"class":409}," 1000",[40,2334,2335],{"class":61},"; i",[40,2337,1176],{"class":53},[40,2339,84],{"class":61},[40,2341,2342,2345,2347,2349,2352,2355,2357,2361,2364,2367],{"class":42,"line":1692},[40,2343,2344],{"class":61},"            jobs ",[40,2346,156],{"class":53},[40,2348,1118],{"class":57},[40,2350,2351],{"class":61},"{ID: i, Data: fmt.",[40,2353,2354],{"class":57},"Sprintf",[40,2356,62],{"class":61},[40,2358,2360],{"class":2359},"sU2Wk","\"data-",[40,2362,2363],{"class":409},"%d",[40,2365,2366],{"class":2359},"\"",[40,2368,2369],{"class":61},", i)}\\n        }\n",[40,2371,2372],{"class":42,"line":1711},[40,2373,171],{"class":61},[40,2375,2376],{"class":42,"line":1716},[40,2377,193],{"emptyLinePlaceholder":192},[40,2379,2380],{"class":42,"line":1722},[40,2381,2382],{"class":46},"    \u002F\u002F Запускаем пул из 10 воркеров\n",[40,2384,2385,2387,2389,2391,2393,2395],{"class":42,"line":1739},[40,2386,1753],{"class":61},[40,2388,93],{"class":53},[40,2390,1903],{"class":57},[40,2392,1703],{"class":61},[40,2394,543],{"class":409},[40,2396,2397],{"class":61},", jobs)\n",[40,2399,2400],{"class":42,"line":1744},[40,2401,193],{"emptyLinePlaceholder":192},[40,2403,2404],{"class":42,"line":1750},[40,2405,2406],{"class":46},"    \u002F\u002F Собираем результаты\n",[40,2408,2409,2411,2414],{"class":42,"line":1768},[40,2410,1412],{"class":53},[40,2412,2413],{"class":61}," errors []",[40,2415,1888],{"class":53},[40,2417,2418,2420,2422,2424,2426],{"class":42,"line":1773},[40,2419,583],{"class":53},[40,2421,586],{"class":61},[40,2423,93],{"class":53},[40,2425,144],{"class":53},[40,2427,1784],{"class":61},[40,2429,2430,2433,2436,2439,2442],{"class":42,"line":1787},[40,2431,2432],{"class":53},"        if",[40,2434,2435],{"class":61}," result.Err ",[40,2437,2438],{"class":53},"!=",[40,2440,2441],{"class":409}," nil",[40,2443,84],{"class":61},[40,2445,2446,2449,2451,2454],{"class":42,"line":1796},[40,2447,2448],{"class":61},"            errors ",[40,2450,1186],{"class":53},[40,2452,2453],{"class":57}," append",[40,2455,2456],{"class":61},"(errors, result.Err)\n",[40,2458,2459],{"class":42,"line":1801},[40,2460,2461],{"class":53},"            continue\n",[40,2463,2465],{"class":42,"line":2464},73,[40,2466,165],{"class":61},[40,2468,2470,2472,2474],{"class":42,"line":2469},74,[40,2471,599],{"class":61},[40,2473,602],{"class":57},[40,2475,2476],{"class":61},"(result.Output)\n",[40,2478,2480],{"class":42,"line":2479},75,[40,2481,614],{"class":61},[40,2483,2485],{"class":42,"line":2484},76,[40,2486,186],{"class":61},[628,2488,2490],{"id":2489},"динамический-worker-pool","Динамический worker pool",[15,2492,2493],{},"Иногда нужно регулировать количество воркеров в рантайме. Простой способ — семафор:",[30,2495,2497],{"className":32,"code":2496,"language":34,"meta":35,"style":35},"type DynamicPool struct {\n    sem chan struct{}\n}\n\nfunc NewDynamicPool(maxWorkers int) *DynamicPool {\n    return &DynamicPool{\n        sem: make(chan struct{}, maxWorkers),\n    }\n}\n\nfunc (p *DynamicPool) Submit(ctx context.Context, fn func()) error {\n    select {\n    case p.sem \u003C- struct{}{}: \u002F\u002F захватываем слот\n    case \u003C-ctx.Done():\n        return ctx.Err()\n    }\n\n    go func() {\n        defer func() { \u003C-p.sem }() \u002F\u002F освобождаем слот\n        fn()\n    }()\n\n    return nil\n}\n\n\u002F\u002F Использование\npool := NewDynamicPool(10) \u002F\u002F максимум 10 одновременных задач\n\nfor i := 0; i \u003C 1000; i++ {\n    i := i\n    if err := pool.Submit(ctx, func() {\n        processItem(i)\n    }); err != nil {\n        break \u002F\u002F контекст отменён\n    }\n}\n",[37,2498,2499,2510,2522,2526,2530,2553,2564,2581,2585,2589,2593,2637,2644,2662,2674,2687,2691,2695,2703,2720,2727,2731,2735,2742,2746,2750,2754,2772,2776,2799,2809,2830,2838,2849,2857,2861],{"__ignoreMap":35},[40,2500,2501,2503,2506,2508],{"class":42,"line":43},[40,2502,1825],{"class":53},[40,2504,2505],{"class":57}," DynamicPool",[40,2507,1830],{"class":53},[40,2509,84],{"class":61},[40,2511,2512,2515,2517,2519],{"class":42,"line":50},[40,2513,2514],{"class":61},"    sem ",[40,2516,101],{"class":53},[40,2518,1830],{"class":53},[40,2520,2521],{"class":61},"{}\n",[40,2523,2524],{"class":42,"line":87},[40,2525,186],{"class":61},[40,2527,2528],{"class":42,"line":109},[40,2529,193],{"emptyLinePlaceholder":192},[40,2531,2532,2534,2537,2539,2542,2544,2546,2548,2551],{"class":42,"line":121},[40,2533,54],{"class":53},[40,2535,2536],{"class":57}," NewDynamicPool",[40,2538,62],{"class":61},[40,2540,2541],{"class":65},"maxWorkers",[40,2543,81],{"class":53},[40,2545,75],{"class":61},[40,2547,287],{"class":53},[40,2549,2550],{"class":57},"DynamicPool",[40,2552,84],{"class":61},[40,2554,2555,2557,2560,2562],{"class":42,"line":133},[40,2556,177],{"class":53},[40,2558,2559],{"class":53}," &",[40,2561,2550],{"class":57},[40,2563,2120],{"class":61},[40,2565,2566,2569,2572,2574,2576,2578],{"class":42,"line":150},[40,2567,2568],{"class":61},"        sem: ",[40,2570,2571],{"class":57},"make",[40,2573,62],{"class":61},[40,2575,101],{"class":53},[40,2577,1830],{"class":53},[40,2579,2580],{"class":61},"{}, maxWorkers),\n",[40,2582,2583],{"class":42,"line":162},[40,2584,614],{"class":61},[40,2586,2587],{"class":42,"line":168},[40,2588,186],{"class":61},[40,2590,2591],{"class":42,"line":174},[40,2592,193],{"emptyLinePlaceholder":192},[40,2594,2595,2597,2600,2603,2605,2607,2609,2612,2614,2616,2618,2620,2622,2624,2627,2629,2632,2635],{"class":42,"line":183},[40,2596,54],{"class":53},[40,2598,2599],{"class":61}," (",[40,2601,2602],{"class":65},"p ",[40,2604,287],{"class":53},[40,2606,2550],{"class":57},[40,2608,75],{"class":61},[40,2610,2611],{"class":57},"Submit",[40,2613,62],{"class":61},[40,2615,651],{"class":65},[40,2617,654],{"class":57},[40,2619,657],{"class":61},[40,2621,660],{"class":57},[40,2623,501],{"class":61},[40,2625,2626],{"class":65},"fn",[40,2628,115],{"class":53},[40,2630,2631],{"class":61},"()) ",[40,2633,2634],{"class":53},"error",[40,2636,84],{"class":61},[40,2638,2639,2642],{"class":42,"line":189},[40,2640,2641],{"class":53},"    select",[40,2643,84],{"class":61},[40,2645,2646,2649,2652,2654,2656,2659],{"class":42,"line":196},[40,2647,2648],{"class":53},"    case",[40,2650,2651],{"class":61}," p.sem ",[40,2653,156],{"class":53},[40,2655,1830],{"class":53},[40,2657,2658],{"class":61},"{}{}: ",[40,2660,2661],{"class":46},"\u002F\u002F захватываем слот\n",[40,2663,2664,2666,2668,2670,2672],{"class":42,"line":202},[40,2665,2648],{"class":53},[40,2667,747],{"class":53},[40,2669,750],{"class":61},[40,2671,753],{"class":57},[40,2673,900],{"class":61},[40,2675,2676,2679,2682,2685],{"class":42,"line":228},[40,2677,2678],{"class":53},"        return",[40,2680,2681],{"class":61}," ctx.",[40,2683,2684],{"class":57},"Err",[40,2686,978],{"class":61},[40,2688,2689],{"class":42,"line":245},[40,2690,614],{"class":61},[40,2692,2693],{"class":42,"line":254},[40,2694,193],{"emptyLinePlaceholder":192},[40,2696,2697,2699,2701],{"class":42,"line":263},[40,2698,112],{"class":53},[40,2700,115],{"class":53},[40,2702,118],{"class":61},[40,2704,2705,2707,2709,2712,2714,2717],{"class":42,"line":278},[40,2706,124],{"class":53},[40,2708,115],{"class":53},[40,2710,2711],{"class":61},"() { ",[40,2713,156],{"class":53},[40,2715,2716],{"class":61},"p.sem }() ",[40,2718,2719],{"class":46},"\u002F\u002F освобождаем слот\n",[40,2721,2722,2725],{"class":42,"line":292},[40,2723,2724],{"class":57},"        fn",[40,2726,978],{"class":61},[40,2728,2729],{"class":42,"line":297},[40,2730,171],{"class":61},[40,2732,2733],{"class":42,"line":302},[40,2734,193],{"emptyLinePlaceholder":192},[40,2736,2737,2739],{"class":42,"line":309},[40,2738,177],{"class":53},[40,2740,2741],{"class":409}," nil\n",[40,2743,2744],{"class":42,"line":314},[40,2745,186],{"class":61},[40,2747,2748],{"class":42,"line":319},[40,2749,193],{"emptyLinePlaceholder":192},[40,2751,2752],{"class":42,"line":325},[40,2753,1643],{"class":46},[40,2755,2756,2759,2761,2763,2765,2767,2769],{"class":42,"line":349},[40,2757,2758],{"class":61},"pool ",[40,2760,93],{"class":53},[40,2762,2536],{"class":57},[40,2764,62],{"class":61},[40,2766,543],{"class":409},[40,2768,75],{"class":61},[40,2770,2771],{"class":46},"\u002F\u002F максимум 10 одновременных задач\n",[40,2773,2774],{"class":42,"line":366},[40,2775,193],{"emptyLinePlaceholder":192},[40,2777,2778,2781,2783,2785,2787,2789,2791,2793,2795,2797],{"class":42,"line":375},[40,2779,2780],{"class":53},"for",[40,2782,1160],{"class":61},[40,2784,93],{"class":53},[40,2786,416],{"class":409},[40,2788,1167],{"class":61},[40,2790,1170],{"class":53},[40,2792,2332],{"class":409},[40,2794,2335],{"class":61},[40,2796,1176],{"class":53},[40,2798,84],{"class":61},[40,2800,2801,2804,2806],{"class":42,"line":384},[40,2802,2803],{"class":61},"    i ",[40,2805,93],{"class":53},[40,2807,2808],{"class":61}," i\n",[40,2810,2811,2814,2817,2819,2822,2824,2826,2828],{"class":42,"line":397},[40,2812,2813],{"class":53},"    if",[40,2815,2816],{"class":61}," err ",[40,2818,93],{"class":53},[40,2820,2821],{"class":61}," pool.",[40,2823,2611],{"class":57},[40,2825,1703],{"class":61},[40,2827,54],{"class":53},[40,2829,118],{"class":61},[40,2831,2832,2835],{"class":42,"line":421},[40,2833,2834],{"class":57},"        processItem",[40,2836,2837],{"class":61},"(i)\n",[40,2839,2840,2843,2845,2847],{"class":42,"line":431},[40,2841,2842],{"class":61},"    }); err ",[40,2844,2438],{"class":53},[40,2846,2441],{"class":409},[40,2848,84],{"class":61},[40,2850,2851,2854],{"class":42,"line":437},[40,2852,2853],{"class":53},"        break",[40,2855,2856],{"class":46}," \u002F\u002F контекст отменён\n",[40,2858,2859],{"class":42,"line":442},[40,2860,614],{"class":61},[40,2862,2863],{"class":42,"line":447},[40,2864,186],{"class":61},[19,2866],{},[22,2868,2870],{"id":2869},"semaphore-ограничение-параллелизма","Semaphore — ограничение параллелизма",[15,2872,2873],{},"Семафор — универсальный инструмент для ограничения количества одновременных операций. В Go идиоматично реализуется через буферизованный канал:",[30,2875,2877],{"className":32,"code":2876,"language":34,"meta":35,"style":35},"\u002F\u002F Ограничиваем до 5 одновременных запросов к внешнему API\nsem := make(chan struct{}, 5)\n\nvar wg sync.WaitGroup\nfor _, url := range urls {\n    wg.Add(1)\n    go func(u string) {\n        defer wg.Done()\n\n        sem \u003C- struct{}{}        \u002F\u002F захватываем слот (блокируется если занято)\n        defer func() { \u003C-sem }() \u002F\u002F освобождаем слот\n\n        fetch(u)\n    }(url)\n}\nwg.Wait()\n",[37,2878,2879,2884,2906,2910,2923,2937,2949,2965,2975,2979,2994,3009,3013,3021,3026,3030],{"__ignoreMap":35},[40,2880,2881],{"class":42,"line":43},[40,2882,2883],{"class":46},"\u002F\u002F Ограничиваем до 5 одновременных запросов к внешнему API\n",[40,2885,2886,2889,2891,2893,2895,2897,2899,2902,2904],{"class":42,"line":50},[40,2887,2888],{"class":61},"sem ",[40,2890,93],{"class":53},[40,2892,96],{"class":57},[40,2894,62],{"class":61},[40,2896,101],{"class":53},[40,2898,1830],{"class":53},[40,2900,2901],{"class":61},"{}, ",[40,2903,518],{"class":409},[40,2905,106],{"class":61},[40,2907,2908],{"class":42,"line":87},[40,2909,193],{"emptyLinePlaceholder":192},[40,2911,2912,2915,2917,2919,2921],{"class":42,"line":109},[40,2913,2914],{"class":53},"var",[40,2916,1415],{"class":61},[40,2918,1418],{"class":57},[40,2920,657],{"class":61},[40,2922,1423],{"class":57},[40,2924,2925,2927,2930,2932,2934],{"class":42,"line":121},[40,2926,2780],{"class":53},[40,2928,2929],{"class":61}," _, url ",[40,2931,93],{"class":53},[40,2933,144],{"class":53},[40,2935,2936],{"class":61}," urls {\n",[40,2938,2939,2941,2943,2945,2947],{"class":42,"line":133},[40,2940,1532],{"class":61},[40,2942,1535],{"class":57},[40,2944,62],{"class":61},[40,2946,498],{"class":409},[40,2948,106],{"class":61},[40,2950,2951,2953,2955,2957,2960,2963],{"class":42,"line":150},[40,2952,112],{"class":53},[40,2954,115],{"class":53},[40,2956,62],{"class":61},[40,2958,2959],{"class":65},"u",[40,2961,2962],{"class":53}," string",[40,2964,1453],{"class":61},[40,2966,2967,2969,2971,2973],{"class":42,"line":162},[40,2968,124],{"class":53},[40,2970,1460],{"class":61},[40,2972,753],{"class":57},[40,2974,978],{"class":61},[40,2976,2977],{"class":42,"line":168},[40,2978,193],{"emptyLinePlaceholder":192},[40,2980,2981,2984,2986,2988,2991],{"class":42,"line":174},[40,2982,2983],{"class":61},"        sem ",[40,2985,156],{"class":53},[40,2987,1830],{"class":53},[40,2989,2990],{"class":61},"{}{}        ",[40,2992,2993],{"class":46},"\u002F\u002F захватываем слот (блокируется если занято)\n",[40,2995,2996,2998,3000,3002,3004,3007],{"class":42,"line":183},[40,2997,124],{"class":53},[40,2999,115],{"class":53},[40,3001,2711],{"class":61},[40,3003,156],{"class":53},[40,3005,3006],{"class":61},"sem }() ",[40,3008,2719],{"class":46},[40,3010,3011],{"class":42,"line":189},[40,3012,193],{"emptyLinePlaceholder":192},[40,3014,3015,3018],{"class":42,"line":196},[40,3016,3017],{"class":57},"        fetch",[40,3019,3020],{"class":61},"(u)\n",[40,3022,3023],{"class":42,"line":202},[40,3024,3025],{"class":61},"    }(url)\n",[40,3027,3028],{"class":42,"line":228},[40,3029,186],{"class":61},[40,3031,3032,3035,3037],{"class":42,"line":245},[40,3033,3034],{"class":61},"wg.",[40,3036,1598],{"class":57},[40,3038,978],{"class":61},[15,3040,3041,3042,3045],{},"Для production-кода лучше использовать ",[37,3043,3044],{},"golang.org\u002Fx\u002Fsync\u002Fsemaphore"," — он поддерживает взятие нескольких слотов за раз и отмену через контекст:",[30,3047,3049],{"className":32,"code":3048,"language":34,"meta":35,"style":35},"import \"golang.org\u002Fx\u002Fsync\u002Fsemaphore\"\n\nsem := semaphore.NewWeighted(5) \u002F\u002F максимум 5 единиц\n\nfor _, url := range urls {\n    \u002F\u002F Захватываем 1 единицу с поддержкой отмены\n    if err := sem.Acquire(ctx, 1); err != nil {\n        break \u002F\u002F контекст отменён\n    }\n\n    go func(u string) {\n        defer sem.Release(1)\n        fetch(u)\n    }(url)\n}\n\n\u002F\u002F Ждём завершения всех горутин\nsem.Acquire(ctx, 5)\n",[37,3050,3051,3064,3068,3089,3093,3105,3110,3137,3143,3147,3151,3165,3180,3186,3190,3194,3198,3203],{"__ignoreMap":35},[40,3052,3053,3056,3059,3061],{"class":42,"line":43},[40,3054,3055],{"class":53},"import",[40,3057,3058],{"class":2359}," \"",[40,3060,3044],{"class":57},[40,3062,3063],{"class":2359},"\"\n",[40,3065,3066],{"class":42,"line":50},[40,3067,193],{"emptyLinePlaceholder":192},[40,3069,3070,3072,3074,3077,3080,3082,3084,3086],{"class":42,"line":87},[40,3071,2888],{"class":61},[40,3073,93],{"class":53},[40,3075,3076],{"class":61}," semaphore.",[40,3078,3079],{"class":57},"NewWeighted",[40,3081,62],{"class":61},[40,3083,518],{"class":409},[40,3085,75],{"class":61},[40,3087,3088],{"class":46},"\u002F\u002F максимум 5 единиц\n",[40,3090,3091],{"class":42,"line":109},[40,3092,193],{"emptyLinePlaceholder":192},[40,3094,3095,3097,3099,3101,3103],{"class":42,"line":121},[40,3096,2780],{"class":53},[40,3098,2929],{"class":61},[40,3100,93],{"class":53},[40,3102,144],{"class":53},[40,3104,2936],{"class":61},[40,3106,3107],{"class":42,"line":133},[40,3108,3109],{"class":46},"    \u002F\u002F Захватываем 1 единицу с поддержкой отмены\n",[40,3111,3112,3114,3116,3118,3121,3124,3126,3128,3131,3133,3135],{"class":42,"line":150},[40,3113,2813],{"class":53},[40,3115,2816],{"class":61},[40,3117,93],{"class":53},[40,3119,3120],{"class":61}," sem.",[40,3122,3123],{"class":57},"Acquire",[40,3125,1703],{"class":61},[40,3127,498],{"class":409},[40,3129,3130],{"class":61},"); err ",[40,3132,2438],{"class":53},[40,3134,2441],{"class":409},[40,3136,84],{"class":61},[40,3138,3139,3141],{"class":42,"line":162},[40,3140,2853],{"class":53},[40,3142,2856],{"class":46},[40,3144,3145],{"class":42,"line":168},[40,3146,614],{"class":61},[40,3148,3149],{"class":42,"line":174},[40,3150,193],{"emptyLinePlaceholder":192},[40,3152,3153,3155,3157,3159,3161,3163],{"class":42,"line":183},[40,3154,112],{"class":53},[40,3156,115],{"class":53},[40,3158,62],{"class":61},[40,3160,2959],{"class":65},[40,3162,2962],{"class":53},[40,3164,1453],{"class":61},[40,3166,3167,3169,3171,3174,3176,3178],{"class":42,"line":189},[40,3168,124],{"class":53},[40,3170,3120],{"class":61},[40,3172,3173],{"class":57},"Release",[40,3175,62],{"class":61},[40,3177,498],{"class":409},[40,3179,106],{"class":61},[40,3181,3182,3184],{"class":42,"line":196},[40,3183,3017],{"class":57},[40,3185,3020],{"class":61},[40,3187,3188],{"class":42,"line":202},[40,3189,3025],{"class":61},[40,3191,3192],{"class":42,"line":228},[40,3193,186],{"class":61},[40,3195,3196],{"class":42,"line":245},[40,3197,193],{"emptyLinePlaceholder":192},[40,3199,3200],{"class":42,"line":254},[40,3201,3202],{"class":46},"\u002F\u002F Ждём завершения всех горутин\n",[40,3204,3205,3208,3210,3212,3214],{"class":42,"line":263},[40,3206,3207],{"class":61},"sem.",[40,3209,3123],{"class":57},[40,3211,1703],{"class":61},[40,3213,518],{"class":409},[40,3215,106],{"class":61},[19,3217],{},[22,3219,3221],{"id":3220},"errgroup-горутины-с-обработкой-ошибок","errgroup — горутины с обработкой ошибок",[15,3223,3224,3227,3228,3231],{},[37,3225,3226],{},"sync.WaitGroup"," не умеет обрабатывать ошибки из горутин. ",[37,3229,3230],{},"golang.org\u002Fx\u002Fsync\u002Ferrgroup"," — его расширение с поддержкой ошибок и контекста:",[30,3233,3235],{"className":32,"code":3234,"language":34,"meta":35,"style":35},"import \"golang.org\u002Fx\u002Fsync\u002Ferrgroup\"\n\nfunc fetchAll(ctx context.Context, urls []string) ([]string, error) {\n    g, ctx := errgroup.WithContext(ctx)\n    results := make([]string, len(urls))\n\n    for i, url := range urls {\n        i, url := i, url \u002F\u002F захватываем переменные\n        g.Go(func() error {\n            resp, err := fetchURL(ctx, url)\n            if err != nil {\n                return fmt.Errorf(\"fetch %s: %w\", url, err)\n            }\n            results[i] = resp \u002F\u002F безопасно: каждый пишет в свой индекс\n            return nil\n        })\n    }\n\n    \u002F\u002F Ждём все горутины. Возвращает первую ошибку если была\n    if err := g.Wait(); err != nil {\n        return nil, err\n    }\n\n    return results, nil\n}\n",[37,3236,3237,3247,3251,3289,3305,3324,3328,3341,3353,3372,3385,3397,3427,3431,3444,3451,3456,3460,3464,3469,3491,3500,3504,3508,3518],{"__ignoreMap":35},[40,3238,3239,3241,3243,3245],{"class":42,"line":43},[40,3240,3055],{"class":53},[40,3242,3058],{"class":2359},[40,3244,3230],{"class":57},[40,3246,3063],{"class":2359},[40,3248,3249],{"class":42,"line":50},[40,3250,193],{"emptyLinePlaceholder":192},[40,3252,3253,3255,3258,3260,3262,3264,3266,3268,3270,3273,3275,3278,3281,3283,3285,3287],{"class":42,"line":87},[40,3254,54],{"class":53},[40,3256,3257],{"class":57}," fetchAll",[40,3259,62],{"class":61},[40,3261,651],{"class":65},[40,3263,654],{"class":57},[40,3265,657],{"class":61},[40,3267,660],{"class":57},[40,3269,501],{"class":61},[40,3271,3272],{"class":65},"urls",[40,3274,69],{"class":61},[40,3276,3277],{"class":53},"string",[40,3279,3280],{"class":61},") ([]",[40,3282,3277],{"class":53},[40,3284,501],{"class":61},[40,3286,2634],{"class":53},[40,3288,1453],{"class":61},[40,3290,3291,3294,3296,3299,3302],{"class":42,"line":109},[40,3292,3293],{"class":61},"    g, ctx ",[40,3295,93],{"class":53},[40,3297,3298],{"class":61}," errgroup.",[40,3300,3301],{"class":57},"WithContext",[40,3303,3304],{"class":61},"(ctx)\n",[40,3306,3307,3309,3311,3313,3315,3317,3319,3321],{"class":42,"line":121},[40,3308,1753],{"class":61},[40,3310,93],{"class":53},[40,3312,96],{"class":57},[40,3314,490],{"class":61},[40,3316,3277],{"class":53},[40,3318,501],{"class":61},[40,3320,1540],{"class":57},[40,3322,3323],{"class":61},"(urls))\n",[40,3325,3326],{"class":42,"line":133},[40,3327,193],{"emptyLinePlaceholder":192},[40,3329,3330,3332,3335,3337,3339],{"class":42,"line":150},[40,3331,583],{"class":53},[40,3333,3334],{"class":61}," i, url ",[40,3336,93],{"class":53},[40,3338,144],{"class":53},[40,3340,2936],{"class":61},[40,3342,3343,3346,3348,3350],{"class":42,"line":162},[40,3344,3345],{"class":61},"        i, url ",[40,3347,93],{"class":53},[40,3349,3334],{"class":61},[40,3351,3352],{"class":46},"\u002F\u002F захватываем переменные\n",[40,3354,3355,3358,3361,3363,3365,3368,3370],{"class":42,"line":168},[40,3356,3357],{"class":61},"        g.",[40,3359,3360],{"class":57},"Go",[40,3362,62],{"class":61},[40,3364,54],{"class":53},[40,3366,3367],{"class":61},"() ",[40,3369,2634],{"class":53},[40,3371,84],{"class":61},[40,3373,3374,3377,3379,3382],{"class":42,"line":174},[40,3375,3376],{"class":61},"            resp, err ",[40,3378,93],{"class":53},[40,3380,3381],{"class":57}," fetchURL",[40,3383,3384],{"class":61},"(ctx, url)\n",[40,3386,3387,3389,3391,3393,3395],{"class":42,"line":183},[40,3388,400],{"class":53},[40,3390,2816],{"class":61},[40,3392,2438],{"class":53},[40,3394,2441],{"class":409},[40,3396,84],{"class":61},[40,3398,3399,3402,3405,3408,3410,3413,3416,3419,3422,3424],{"class":42,"line":189},[40,3400,3401],{"class":53},"                return",[40,3403,3404],{"class":61}," fmt.",[40,3406,3407],{"class":57},"Errorf",[40,3409,62],{"class":61},[40,3411,3412],{"class":2359},"\"fetch ",[40,3414,3415],{"class":409},"%s",[40,3417,3418],{"class":2359},": ",[40,3420,3421],{"class":409},"%w",[40,3423,2366],{"class":2359},[40,3425,3426],{"class":61},", url, err)\n",[40,3428,3429],{"class":42,"line":196},[40,3430,434],{"class":61},[40,3432,3433,3436,3438,3441],{"class":42,"line":202},[40,3434,3435],{"class":61},"            results[i] ",[40,3437,1186],{"class":53},[40,3439,3440],{"class":61}," resp ",[40,3442,3443],{"class":46},"\u002F\u002F безопасно: каждый пишет в свой индекс\n",[40,3445,3446,3449],{"class":42,"line":228},[40,3447,3448],{"class":53},"            return",[40,3450,2741],{"class":409},[40,3452,3453],{"class":42,"line":245},[40,3454,3455],{"class":61},"        })\n",[40,3457,3458],{"class":42,"line":254},[40,3459,614],{"class":61},[40,3461,3462],{"class":42,"line":263},[40,3463,193],{"emptyLinePlaceholder":192},[40,3465,3466],{"class":42,"line":278},[40,3467,3468],{"class":46},"    \u002F\u002F Ждём все горутины. Возвращает первую ошибку если была\n",[40,3470,3471,3473,3475,3477,3480,3482,3485,3487,3489],{"class":42,"line":292},[40,3472,2813],{"class":53},[40,3474,2816],{"class":61},[40,3476,93],{"class":53},[40,3478,3479],{"class":61}," g.",[40,3481,1598],{"class":57},[40,3483,3484],{"class":61},"(); err ",[40,3486,2438],{"class":53},[40,3488,2441],{"class":409},[40,3490,84],{"class":61},[40,3492,3493,3495,3497],{"class":42,"line":297},[40,3494,2678],{"class":53},[40,3496,2441],{"class":409},[40,3498,3499],{"class":61},", err\n",[40,3501,3502],{"class":42,"line":302},[40,3503,614],{"class":61},[40,3505,3506],{"class":42,"line":309},[40,3507,193],{"emptyLinePlaceholder":192},[40,3509,3510,3512,3515],{"class":42,"line":314},[40,3511,177],{"class":53},[40,3513,3514],{"class":61}," results, ",[40,3516,3517],{"class":409},"nil\n",[40,3519,3520],{"class":42,"line":319},[40,3521,186],{"class":61},[15,3523,3524,3527,3528,3531],{},[37,3525,3526],{},"errgroup.WithContext"," возвращает контекст, который автоматически отменяется при первой ошибке в любой горутине. Остальные горутины должны проверять ",[37,3529,3530],{},"ctx.Done()"," чтобы корректно завершиться.",[628,3533,3535],{"id":3534},"errgroup-с-ограничением-параллелизма","errgroup с ограничением параллелизма",[30,3537,3539],{"className":32,"code":3538,"language":34,"meta":35,"style":35},"g, ctx := errgroup.WithContext(ctx)\ng.SetLimit(10) \u002F\u002F максимум 10 горутин одновременно\n\nfor _, job := range jobs {\n    job := job\n    g.Go(func() error { \u002F\u002F блокируется если уже 10 горутин работает\n        return process(ctx, job)\n    })\n}\n\nif err := g.Wait(); err != nil {\n    return err\n}\n",[37,3540,3541,3554,3571,3575,3589,3599,3620,3629,3634,3638,3642,3663,3670],{"__ignoreMap":35},[40,3542,3543,3546,3548,3550,3552],{"class":42,"line":43},[40,3544,3545],{"class":61},"g, ctx ",[40,3547,93],{"class":53},[40,3549,3298],{"class":61},[40,3551,3301],{"class":57},[40,3553,3304],{"class":61},[40,3555,3556,3559,3562,3564,3566,3568],{"class":42,"line":50},[40,3557,3558],{"class":61},"g.",[40,3560,3561],{"class":57},"SetLimit",[40,3563,62],{"class":61},[40,3565,543],{"class":409},[40,3567,75],{"class":61},[40,3569,3570],{"class":46},"\u002F\u002F максимум 10 горутин одновременно\n",[40,3572,3573],{"class":42,"line":87},[40,3574,193],{"emptyLinePlaceholder":192},[40,3576,3577,3579,3582,3584,3586],{"class":42,"line":109},[40,3578,2780],{"class":53},[40,3580,3581],{"class":61}," _, job ",[40,3583,93],{"class":53},[40,3585,144],{"class":53},[40,3587,3588],{"class":61}," jobs {\n",[40,3590,3591,3594,3596],{"class":42,"line":121},[40,3592,3593],{"class":61},"    job ",[40,3595,93],{"class":53},[40,3597,3598],{"class":61}," job\n",[40,3600,3601,3604,3606,3608,3610,3612,3614,3617],{"class":42,"line":133},[40,3602,3603],{"class":61},"    g.",[40,3605,3360],{"class":57},[40,3607,62],{"class":61},[40,3609,54],{"class":53},[40,3611,3367],{"class":61},[40,3613,2634],{"class":53},[40,3615,3616],{"class":61}," { ",[40,3618,3619],{"class":46},"\u002F\u002F блокируется если уже 10 горутин работает\n",[40,3621,3622,3624,3626],{"class":42,"line":150},[40,3623,2678],{"class":53},[40,3625,1321],{"class":57},[40,3627,3628],{"class":61},"(ctx, job)\n",[40,3630,3631],{"class":42,"line":162},[40,3632,3633],{"class":61},"    })\n",[40,3635,3636],{"class":42,"line":168},[40,3637,186],{"class":61},[40,3639,3640],{"class":42,"line":174},[40,3641,193],{"emptyLinePlaceholder":192},[40,3643,3644,3647,3649,3651,3653,3655,3657,3659,3661],{"class":42,"line":183},[40,3645,3646],{"class":53},"if",[40,3648,2816],{"class":61},[40,3650,93],{"class":53},[40,3652,3479],{"class":61},[40,3654,1598],{"class":57},[40,3656,3484],{"class":61},[40,3658,2438],{"class":53},[40,3660,2441],{"class":409},[40,3662,84],{"class":61},[40,3664,3665,3667],{"class":42,"line":189},[40,3666,177],{"class":53},[40,3668,3669],{"class":61}," err\n",[40,3671,3672],{"class":42,"line":196},[40,3673,186],{"class":61},[15,3675,3676,3678],{},[37,3677,3561],{}," появился в Go 1.20 — встроенный семафор в errgroup, не нужно отдельно управлять.",[19,3680],{},[22,3682,3684],{"id":3683},"timeout-и-retry","Timeout и Retry",[15,3686,3687],{},"Два паттерна, которые часто нужны вместе при работе с внешними сервисами:",[30,3689,3691],{"className":32,"code":3690,"language":34,"meta":35,"style":35},"\u002F\u002F Timeout: ограничение времени на операцию\nfunc withTimeout(ctx context.Context, timeout time.Duration, fn func(ctx context.Context) error) error {\n    ctx, cancel := context.WithTimeout(ctx, timeout)\n    defer cancel()\n    return fn(ctx)\n}\n\n\u002F\u002F Retry с экспоненциальным backoff\nfunc withRetry(ctx context.Context, attempts int, fn func() error) error {\n    var err error\n    for i := 0; i \u003C attempts; i++ {\n        if err = fn(); err == nil {\n            return nil\n        }\n\n        \u002F\u002F Не ретраим если контекст отменён\n        if ctx.Err() != nil {\n            return ctx.Err()\n        }\n\n        \u002F\u002F Экспоненциальный backoff: 100ms, 200ms, 400ms...\n        backoff := time.Duration(1\u003C\u003Cuint(i)) * 100 * time.Millisecond\n        select {\n        case \u003C-time.After(backoff):\n        case \u003C-ctx.Done():\n            return ctx.Err()\n        }\n    }\n    return fmt.Errorf(\"все %d попыток исчерпаны: %w\", attempts, err)\n}\n\n\u002F\u002F Комбинируем\nfunc callAPI(ctx context.Context) error {\n    return withRetry(ctx, 3, func() error {\n        return withTimeout(ctx, 2*time.Second, func(ctx context.Context) error {\n            return httpCall(ctx)\n        })\n    })\n}\n",[37,3692,3693,3698,3754,3767,3775,3784,3788,3792,3797,3837,3845,3866,3885,3891,3895,3899,3904,3920,3930,3934,3938,3943,3976,3983,3999,4011,4021,4025,4029,4054,4058,4062,4067,4090,4110,4143,4152,4156,4160],{"__ignoreMap":35},[40,3694,3695],{"class":42,"line":43},[40,3696,3697],{"class":46},"\u002F\u002F Timeout: ограничение времени на операцию\n",[40,3699,3700,3702,3705,3707,3709,3711,3713,3715,3717,3720,3723,3725,3728,3730,3732,3734,3736,3738,3740,3742,3744,3746,3748,3750,3752],{"class":42,"line":50},[40,3701,54],{"class":53},[40,3703,3704],{"class":57}," withTimeout",[40,3706,62],{"class":61},[40,3708,651],{"class":65},[40,3710,654],{"class":57},[40,3712,657],{"class":61},[40,3714,660],{"class":57},[40,3716,501],{"class":61},[40,3718,3719],{"class":65},"timeout",[40,3721,3722],{"class":57}," time",[40,3724,657],{"class":61},[40,3726,3727],{"class":57},"Duration",[40,3729,501],{"class":61},[40,3731,2626],{"class":65},[40,3733,115],{"class":53},[40,3735,62],{"class":61},[40,3737,651],{"class":65},[40,3739,654],{"class":57},[40,3741,657],{"class":61},[40,3743,660],{"class":57},[40,3745,75],{"class":61},[40,3747,2634],{"class":53},[40,3749,75],{"class":61},[40,3751,2634],{"class":53},[40,3753,84],{"class":61},[40,3755,3756,3758,3760,3762,3764],{"class":42,"line":87},[40,3757,943],{"class":61},[40,3759,93],{"class":53},[40,3761,948],{"class":61},[40,3763,951],{"class":57},[40,3765,3766],{"class":61},"(ctx, timeout)\n",[40,3768,3769,3771,3773],{"class":42,"line":109},[40,3770,972],{"class":53},[40,3772,975],{"class":57},[40,3774,978],{"class":61},[40,3776,3777,3779,3782],{"class":42,"line":121},[40,3778,177],{"class":53},[40,3780,3781],{"class":57}," fn",[40,3783,3304],{"class":61},[40,3785,3786],{"class":42,"line":133},[40,3787,186],{"class":61},[40,3789,3790],{"class":42,"line":150},[40,3791,193],{"emptyLinePlaceholder":192},[40,3793,3794],{"class":42,"line":162},[40,3795,3796],{"class":46},"\u002F\u002F Retry с экспоненциальным backoff\n",[40,3798,3799,3801,3804,3806,3808,3810,3812,3814,3816,3819,3821,3823,3825,3827,3829,3831,3833,3835],{"class":42,"line":168},[40,3800,54],{"class":53},[40,3802,3803],{"class":57}," withRetry",[40,3805,62],{"class":61},[40,3807,651],{"class":65},[40,3809,654],{"class":57},[40,3811,657],{"class":61},[40,3813,660],{"class":57},[40,3815,501],{"class":61},[40,3817,3818],{"class":65},"attempts",[40,3820,81],{"class":53},[40,3822,501],{"class":61},[40,3824,2626],{"class":65},[40,3826,115],{"class":53},[40,3828,3367],{"class":61},[40,3830,2634],{"class":53},[40,3832,75],{"class":61},[40,3834,2634],{"class":53},[40,3836,84],{"class":61},[40,3838,3839,3841,3843],{"class":42,"line":174},[40,3840,1412],{"class":53},[40,3842,2816],{"class":61},[40,3844,1888],{"class":53},[40,3846,3847,3849,3851,3853,3855,3857,3859,3862,3864],{"class":42,"line":183},[40,3848,583],{"class":53},[40,3850,1160],{"class":61},[40,3852,93],{"class":53},[40,3854,416],{"class":409},[40,3856,1167],{"class":61},[40,3858,1170],{"class":53},[40,3860,3861],{"class":61}," attempts; i",[40,3863,1176],{"class":53},[40,3865,84],{"class":61},[40,3867,3868,3870,3872,3874,3876,3878,3881,3883],{"class":42,"line":189},[40,3869,2432],{"class":53},[40,3871,2816],{"class":61},[40,3873,1186],{"class":53},[40,3875,3781],{"class":57},[40,3877,3484],{"class":61},[40,3879,3880],{"class":53},"==",[40,3882,2441],{"class":409},[40,3884,84],{"class":61},[40,3886,3887,3889],{"class":42,"line":196},[40,3888,3448],{"class":53},[40,3890,2741],{"class":409},[40,3892,3893],{"class":42,"line":202},[40,3894,165],{"class":61},[40,3896,3897],{"class":42,"line":228},[40,3898,193],{"emptyLinePlaceholder":192},[40,3900,3901],{"class":42,"line":245},[40,3902,3903],{"class":46},"        \u002F\u002F Не ретраим если контекст отменён\n",[40,3905,3906,3908,3910,3912,3914,3916,3918],{"class":42,"line":254},[40,3907,2432],{"class":53},[40,3909,2681],{"class":61},[40,3911,2684],{"class":57},[40,3913,3367],{"class":61},[40,3915,2438],{"class":53},[40,3917,2441],{"class":409},[40,3919,84],{"class":61},[40,3921,3922,3924,3926,3928],{"class":42,"line":263},[40,3923,3448],{"class":53},[40,3925,2681],{"class":61},[40,3927,2684],{"class":57},[40,3929,978],{"class":61},[40,3931,3932],{"class":42,"line":278},[40,3933,165],{"class":61},[40,3935,3936],{"class":42,"line":292},[40,3937,193],{"emptyLinePlaceholder":192},[40,3939,3940],{"class":42,"line":297},[40,3941,3942],{"class":46},"        \u002F\u002F Экспоненциальный backoff: 100ms, 200ms, 400ms...\n",[40,3944,3945,3948,3950,3953,3955,3957,3959,3962,3965,3967,3970,3973],{"class":42,"line":302},[40,3946,3947],{"class":61},"        backoff ",[40,3949,93],{"class":53},[40,3951,3952],{"class":61}," time.",[40,3954,3727],{"class":57},[40,3956,62],{"class":61},[40,3958,498],{"class":409},[40,3960,3961],{"class":53},"\u003C\u003Cuint",[40,3963,3964],{"class":61},"(i)) ",[40,3966,287],{"class":53},[40,3968,3969],{"class":409}," 100",[40,3971,3972],{"class":53}," *",[40,3974,3975],{"class":61}," time.Millisecond\n",[40,3977,3978,3981],{"class":42,"line":309},[40,3979,3980],{"class":53},"        select",[40,3982,84],{"class":61},[40,3984,3985,3988,3990,3993,3996],{"class":42,"line":314},[40,3986,3987],{"class":53},"        case",[40,3989,747],{"class":53},[40,3991,3992],{"class":61},"time.",[40,3994,3995],{"class":57},"After",[40,3997,3998],{"class":61},"(backoff):\n",[40,4000,4001,4003,4005,4007,4009],{"class":42,"line":319},[40,4002,3987],{"class":53},[40,4004,747],{"class":53},[40,4006,750],{"class":61},[40,4008,753],{"class":57},[40,4010,900],{"class":61},[40,4012,4013,4015,4017,4019],{"class":42,"line":325},[40,4014,3448],{"class":53},[40,4016,2681],{"class":61},[40,4018,2684],{"class":57},[40,4020,978],{"class":61},[40,4022,4023],{"class":42,"line":349},[40,4024,165],{"class":61},[40,4026,4027],{"class":42,"line":366},[40,4028,614],{"class":61},[40,4030,4031,4033,4035,4037,4039,4042,4044,4047,4049,4051],{"class":42,"line":375},[40,4032,177],{"class":53},[40,4034,3404],{"class":61},[40,4036,3407],{"class":57},[40,4038,62],{"class":61},[40,4040,4041],{"class":2359},"\"все ",[40,4043,2363],{"class":409},[40,4045,4046],{"class":2359}," попыток исчерпаны: ",[40,4048,3421],{"class":409},[40,4050,2366],{"class":2359},[40,4052,4053],{"class":61},", attempts, err)\n",[40,4055,4056],{"class":42,"line":384},[40,4057,186],{"class":61},[40,4059,4060],{"class":42,"line":397},[40,4061,193],{"emptyLinePlaceholder":192},[40,4063,4064],{"class":42,"line":421},[40,4065,4066],{"class":46},"\u002F\u002F Комбинируем\n",[40,4068,4069,4071,4074,4076,4078,4080,4082,4084,4086,4088],{"class":42,"line":431},[40,4070,54],{"class":53},[40,4072,4073],{"class":57}," callAPI",[40,4075,62],{"class":61},[40,4077,651],{"class":65},[40,4079,654],{"class":57},[40,4081,657],{"class":61},[40,4083,660],{"class":57},[40,4085,75],{"class":61},[40,4087,2634],{"class":53},[40,4089,84],{"class":61},[40,4091,4092,4094,4096,4098,4100,4102,4104,4106,4108],{"class":42,"line":437},[40,4093,177],{"class":53},[40,4095,3803],{"class":57},[40,4097,1703],{"class":61},[40,4099,508],{"class":409},[40,4101,501],{"class":61},[40,4103,54],{"class":53},[40,4105,3367],{"class":61},[40,4107,2634],{"class":53},[40,4109,84],{"class":61},[40,4111,4112,4114,4116,4118,4120,4122,4125,4127,4129,4131,4133,4135,4137,4139,4141],{"class":42,"line":442},[40,4113,2678],{"class":53},[40,4115,3704],{"class":57},[40,4117,1703],{"class":61},[40,4119,410],{"class":409},[40,4121,287],{"class":53},[40,4123,4124],{"class":61},"time.Second, ",[40,4126,54],{"class":53},[40,4128,62],{"class":61},[40,4130,651],{"class":65},[40,4132,654],{"class":57},[40,4134,657],{"class":61},[40,4136,660],{"class":57},[40,4138,75],{"class":61},[40,4140,2634],{"class":53},[40,4142,84],{"class":61},[40,4144,4145,4147,4150],{"class":42,"line":447},[40,4146,3448],{"class":53},[40,4148,4149],{"class":57}," httpCall",[40,4151,3304],{"class":61},[40,4153,4154],{"class":42,"line":454},[40,4155,3455],{"class":61},[40,4157,4158],{"class":42,"line":459},[40,4159,3633],{"class":61},[40,4161,4162],{"class":42,"line":464},[40,4163,186],{"class":61},[19,4165],{},[22,4167,4169],{"id":4168},"or-done-чтение-с-отменой","Or-Done — чтение с отменой",[15,4171,4172,4173,4176],{},"Когда нужно читать из канала с возможностью прерывания через Done — повторяющийся ",[37,4174,4175],{},"select"," можно вынести в хелпер:",[30,4178,4180],{"className":32,"code":4179,"language":34,"meta":35,"style":35},"\u002F\u002F orDone оборачивает чтение из канала с поддержкой отмены\nfunc orDone(ctx context.Context, ch \u003C-chan int) \u003C-chan int {\n    out := make(chan int)\n    go func() {\n        defer close(out)\n        for {\n            select {\n            case \u003C-ctx.Done():\n                return\n            case v, ok := \u003C-ch:\n                if !ok {\n                    return\n                }\n                select {\n                case out \u003C- v:\n                case \u003C-ctx.Done():\n                    return\n                }\n            }\n        }\n    }()\n    return out\n}\n\n\u002F\u002F Использование — вместо повторяющегося select везде\nfor val := range orDone(ctx, source) {\n    process(val)\n}\n",[37,4181,4182,4187,4220,4236,4244,4252,4258,4264,4276,4280,4294,4303,4307,4311,4317,4328,4340,4344,4348,4352,4356,4360,4366,4370,4374,4379,4395,4403],{"__ignoreMap":35},[40,4183,4184],{"class":42,"line":43},[40,4185,4186],{"class":46},"\u002F\u002F orDone оборачивает чтение из канала с поддержкой отмены\n",[40,4188,4189,4191,4194,4196,4198,4200,4202,4204,4206,4208,4210,4212,4214,4216,4218],{"class":42,"line":50},[40,4190,54],{"class":53},[40,4192,4193],{"class":57}," orDone",[40,4195,62],{"class":61},[40,4197,651],{"class":65},[40,4199,654],{"class":57},[40,4201,657],{"class":61},[40,4203,660],{"class":57},[40,4205,501],{"class":61},[40,4207,1446],{"class":65},[40,4209,215],{"class":53},[40,4211,81],{"class":53},[40,4213,75],{"class":61},[40,4215,78],{"class":53},[40,4217,81],{"class":53},[40,4219,84],{"class":61},[40,4221,4222,4224,4226,4228,4230,4232,4234],{"class":42,"line":87},[40,4223,90],{"class":61},[40,4225,93],{"class":53},[40,4227,96],{"class":57},[40,4229,62],{"class":61},[40,4231,101],{"class":53},[40,4233,81],{"class":53},[40,4235,106],{"class":61},[40,4237,4238,4240,4242],{"class":42,"line":109},[40,4239,112],{"class":53},[40,4241,115],{"class":53},[40,4243,118],{"class":61},[40,4245,4246,4248,4250],{"class":42,"line":121},[40,4247,124],{"class":53},[40,4249,127],{"class":57},[40,4251,130],{"class":61},[40,4253,4254,4256],{"class":42,"line":133},[40,4255,136],{"class":53},[40,4257,84],{"class":61},[40,4259,4260,4262],{"class":42,"line":150},[40,4261,725],{"class":53},[40,4263,84],{"class":61},[40,4265,4266,4268,4270,4272,4274],{"class":42,"line":162},[40,4267,732],{"class":53},[40,4269,747],{"class":53},[40,4271,750],{"class":61},[40,4273,753],{"class":57},[40,4275,900],{"class":61},[40,4277,4278],{"class":42,"line":168},[40,4279,764],{"class":53},[40,4281,4282,4284,4287,4289,4291],{"class":42,"line":174},[40,4283,732],{"class":53},[40,4285,4286],{"class":61}," v, ok ",[40,4288,93],{"class":53},[40,4290,747],{"class":53},[40,4292,4293],{"class":61},"ch:\n",[40,4295,4296,4299,4301],{"class":42,"line":183},[40,4297,4298],{"class":53},"                if",[40,4300,2079],{"class":53},[40,4302,2082],{"class":61},[40,4304,4305],{"class":42,"line":189},[40,4306,2156],{"class":53},[40,4308,4309],{"class":42,"line":196},[40,4310,2161],{"class":61},[40,4312,4313,4315],{"class":42,"line":202},[40,4314,2054],{"class":53},[40,4316,84],{"class":61},[40,4318,4319,4321,4323,4325],{"class":42,"line":228},[40,4320,2061],{"class":53},[40,4322,735],{"class":61},[40,4324,156],{"class":53},[40,4326,4327],{"class":61}," v:\n",[40,4329,4330,4332,4334,4336,4338],{"class":42,"line":245},[40,4331,2061],{"class":53},[40,4333,747],{"class":53},[40,4335,750],{"class":61},[40,4337,753],{"class":57},[40,4339,900],{"class":61},[40,4341,4342],{"class":42,"line":254},[40,4343,2156],{"class":53},[40,4345,4346],{"class":42,"line":263},[40,4347,2161],{"class":61},[40,4349,4350],{"class":42,"line":278},[40,4351,434],{"class":61},[40,4353,4354],{"class":42,"line":292},[40,4355,165],{"class":61},[40,4357,4358],{"class":42,"line":297},[40,4359,171],{"class":61},[40,4361,4362,4364],{"class":42,"line":302},[40,4363,177],{"class":53},[40,4365,180],{"class":61},[40,4367,4368],{"class":42,"line":309},[40,4369,186],{"class":61},[40,4371,4372],{"class":42,"line":314},[40,4373,193],{"emptyLinePlaceholder":192},[40,4375,4376],{"class":42,"line":319},[40,4377,4378],{"class":46},"\u002F\u002F Использование — вместо повторяющегося select везде\n",[40,4380,4381,4383,4386,4388,4390,4392],{"class":42,"line":325},[40,4382,2780],{"class":53},[40,4384,4385],{"class":61}," val ",[40,4387,93],{"class":53},[40,4389,144],{"class":53},[40,4391,4193],{"class":57},[40,4393,4394],{"class":61},"(ctx, source) {\n",[40,4396,4397,4400],{"class":42,"line":349},[40,4398,4399],{"class":57},"    process",[40,4401,4402],{"class":61},"(val)\n",[40,4404,4405],{"class":42,"line":366},[40,4406,186],{"class":61},[19,4408],{},[22,4410,4412],{"id":4411},"tee-разветвление-канала","Tee — разветвление канала",[15,4414,4415],{},"Иногда нужно направить данные из одного канала в два потребителя:",[30,4417,4419],{"className":32,"code":4418,"language":34,"meta":35,"style":35},"func tee(ctx context.Context, in \u003C-chan int) (\u003C-chan int, \u003C-chan int) {\n    out1 := make(chan int)\n    out2 := make(chan int)\n\n    go func() {\n        defer close(out1)\n        defer close(out2)\n        for val := range orDone(ctx, in) {\n            \u002F\u002F Локальные копии для двух select\n            o1, o2 := out1, out2\n            \u002F\u002F Отправляем в оба канала — используем два select\n            \u002F\u002F чтобы не зависеть от порядка готовности\n            for i := 0; i \u003C 2; i++ {\n                select {\n                case o1 \u003C- val:\n                    o1 = nil \u002F\u002F уже отправили, исключаем из следующего select\n                case o2 \u003C- val:\n                    o2 = nil\n                }\n            }\n        }\n    }()\n\n    return out1, out2\n}\n\n\u002F\u002F Использование\nsource := generate(ctx, data)\nforLogger, forProcessor := tee(ctx, source)\n\ngo logAll(forLogger)\nprocessAll(forProcessor)\n",[37,4420,4421,4461,4478,4495,4499,4507,4516,4525,4540,4545,4555,4560,4565,4588,4594,4606,4618,4629,4638,4642,4646,4650,4654,4658,4664,4668,4672,4676,4688,4700,4704,4714],{"__ignoreMap":35},[40,4422,4423,4425,4428,4430,4432,4434,4436,4438,4440,4442,4444,4446,4449,4451,4453,4455,4457,4459],{"class":42,"line":43},[40,4424,54],{"class":53},[40,4426,4427],{"class":57}," tee",[40,4429,62],{"class":61},[40,4431,651],{"class":65},[40,4433,654],{"class":57},[40,4435,657],{"class":61},[40,4437,660],{"class":57},[40,4439,501],{"class":61},[40,4441,212],{"class":65},[40,4443,215],{"class":53},[40,4445,81],{"class":53},[40,4447,4448],{"class":61},") (",[40,4450,78],{"class":53},[40,4452,81],{"class":53},[40,4454,501],{"class":61},[40,4456,78],{"class":53},[40,4458,81],{"class":53},[40,4460,1453],{"class":61},[40,4462,4463,4466,4468,4470,4472,4474,4476],{"class":42,"line":50},[40,4464,4465],{"class":61},"    out1 ",[40,4467,93],{"class":53},[40,4469,96],{"class":57},[40,4471,62],{"class":61},[40,4473,101],{"class":53},[40,4475,81],{"class":53},[40,4477,106],{"class":61},[40,4479,4480,4483,4485,4487,4489,4491,4493],{"class":42,"line":87},[40,4481,4482],{"class":61},"    out2 ",[40,4484,93],{"class":53},[40,4486,96],{"class":57},[40,4488,62],{"class":61},[40,4490,101],{"class":53},[40,4492,81],{"class":53},[40,4494,106],{"class":61},[40,4496,4497],{"class":42,"line":109},[40,4498,193],{"emptyLinePlaceholder":192},[40,4500,4501,4503,4505],{"class":42,"line":121},[40,4502,112],{"class":53},[40,4504,115],{"class":53},[40,4506,118],{"class":61},[40,4508,4509,4511,4513],{"class":42,"line":133},[40,4510,124],{"class":53},[40,4512,127],{"class":57},[40,4514,4515],{"class":61},"(out1)\n",[40,4517,4518,4520,4522],{"class":42,"line":150},[40,4519,124],{"class":53},[40,4521,127],{"class":57},[40,4523,4524],{"class":61},"(out2)\n",[40,4526,4527,4529,4531,4533,4535,4537],{"class":42,"line":162},[40,4528,136],{"class":53},[40,4530,4385],{"class":61},[40,4532,93],{"class":53},[40,4534,144],{"class":53},[40,4536,4193],{"class":57},[40,4538,4539],{"class":61},"(ctx, in) {\n",[40,4541,4542],{"class":42,"line":168},[40,4543,4544],{"class":46},"            \u002F\u002F Локальные копии для двух select\n",[40,4546,4547,4550,4552],{"class":42,"line":174},[40,4548,4549],{"class":61},"            o1, o2 ",[40,4551,93],{"class":53},[40,4553,4554],{"class":61}," out1, out2\n",[40,4556,4557],{"class":42,"line":183},[40,4558,4559],{"class":46},"            \u002F\u002F Отправляем в оба канала — используем два select\n",[40,4561,4562],{"class":42,"line":189},[40,4563,4564],{"class":46},"            \u002F\u002F чтобы не зависеть от порядка готовности\n",[40,4566,4567,4569,4571,4573,4575,4577,4579,4582,4584,4586],{"class":42,"line":196},[40,4568,2047],{"class":53},[40,4570,1160],{"class":61},[40,4572,93],{"class":53},[40,4574,416],{"class":409},[40,4576,1167],{"class":61},[40,4578,1170],{"class":53},[40,4580,4581],{"class":409}," 2",[40,4583,2335],{"class":61},[40,4585,1176],{"class":53},[40,4587,84],{"class":61},[40,4589,4590,4592],{"class":42,"line":202},[40,4591,2054],{"class":53},[40,4593,84],{"class":61},[40,4595,4596,4598,4601,4603],{"class":42,"line":228},[40,4597,2061],{"class":53},[40,4599,4600],{"class":61}," o1 ",[40,4602,156],{"class":53},[40,4604,4605],{"class":61}," val:\n",[40,4607,4608,4611,4613,4615],{"class":42,"line":245},[40,4609,4610],{"class":61},"                    o1 ",[40,4612,1186],{"class":53},[40,4614,2441],{"class":409},[40,4616,4617],{"class":46}," \u002F\u002F уже отправили, исключаем из следующего select\n",[40,4619,4620,4622,4625,4627],{"class":42,"line":254},[40,4621,2061],{"class":53},[40,4623,4624],{"class":61}," o2 ",[40,4626,156],{"class":53},[40,4628,4605],{"class":61},[40,4630,4631,4634,4636],{"class":42,"line":263},[40,4632,4633],{"class":61},"                    o2 ",[40,4635,1186],{"class":53},[40,4637,2741],{"class":409},[40,4639,4640],{"class":42,"line":278},[40,4641,2161],{"class":61},[40,4643,4644],{"class":42,"line":292},[40,4645,434],{"class":61},[40,4647,4648],{"class":42,"line":297},[40,4649,165],{"class":61},[40,4651,4652],{"class":42,"line":302},[40,4653,171],{"class":61},[40,4655,4656],{"class":42,"line":309},[40,4657,193],{"emptyLinePlaceholder":192},[40,4659,4660,4662],{"class":42,"line":314},[40,4661,177],{"class":53},[40,4663,4554],{"class":61},[40,4665,4666],{"class":42,"line":319},[40,4667,186],{"class":61},[40,4669,4670],{"class":42,"line":325},[40,4671,193],{"emptyLinePlaceholder":192},[40,4673,4674],{"class":42,"line":349},[40,4675,1643],{"class":46},[40,4677,4678,4681,4683,4685],{"class":42,"line":366},[40,4679,4680],{"class":61},"source ",[40,4682,93],{"class":53},[40,4684,58],{"class":57},[40,4686,4687],{"class":61},"(ctx, data)\n",[40,4689,4690,4693,4695,4697],{"class":42,"line":375},[40,4691,4692],{"class":61},"forLogger, forProcessor ",[40,4694,93],{"class":53},[40,4696,4427],{"class":57},[40,4698,4699],{"class":61},"(ctx, source)\n",[40,4701,4702],{"class":42,"line":384},[40,4703,193],{"emptyLinePlaceholder":192},[40,4705,4706,4708,4711],{"class":42,"line":397},[40,4707,34],{"class":53},[40,4709,4710],{"class":57}," logAll",[40,4712,4713],{"class":61},"(forLogger)\n",[40,4715,4716,4719],{"class":42,"line":421},[40,4717,4718],{"class":57},"processAll",[40,4720,4721],{"class":61},"(forProcessor)\n",[19,4723],{},[22,4725,4727],{"id":4726},"выбор-паттерна","Выбор паттерна",[15,4729,4730],{},"На практике выбор между паттернами определяется характером задачи:",[4732,4733,4734,4747],"table",{},[4735,4736,4737],"thead",{},[4738,4739,4740,4744],"tr",{},[4741,4742,4743],"th",{},"Задача",[4741,4745,4746],{},"Паттерн",[4748,4749,4750,4759,4766,4773,4781,4789,4797],"tbody",{},[4738,4751,4752,4756],{},[4753,4754,4755],"td",{},"Последовательная трансформация данных",[4753,4757,4758],{},"Pipeline",[4738,4760,4761,4764],{},[4753,4762,4763],{},"Один источник, много обработчиков",[4753,4765,1077],{},[4738,4767,4768,4771],{},[4753,4769,4770],{},"Много источников, один потребитель",[4753,4772,1081],{},[4738,4774,4775,4778],{},[4753,4776,4777],{},"Очередь задач с ограниченным параллелизмом",[4753,4779,4780],{},"Worker Pool",[4738,4782,4783,4786],{},[4753,4784,4785],{},"Ограничить конкурентность существующего кода",[4753,4787,4788],{},"Semaphore",[4738,4790,4791,4794],{},[4753,4792,4793],{},"Параллельные задачи с обработкой ошибок",[4753,4795,4796],{},"errgroup",[4738,4798,4799,4802],{},[4753,4800,4801],{},"Внешние вызовы с ненадёжным сервисом",[4753,4803,4804],{},"Timeout + Retry",[15,4806,4807],{},"На практике паттерны комбинируются: типичный backend-сервис использует worker pool для обработки входящих запросов, errgroup для параллельных запросов к зависимостям, semaphore для ограничения нагрузки на БД, и pipeline для трансформации данных внутри обработчика.",[19,4809],{},[22,4811,4813],{"id":4812},"вопросы-на-собеседовании","Вопросы на собеседовании",[15,4815,4816,4819,4822],{},[1075,4817,4818],{},"Q: Что такое pipeline? В чём его преимущества?",[4820,4821],"br",{},"\nA: Последовательность стадий обработки, соединённых каналами. Каждая стадия — независимая горутина, читает из входного канала и пишет в выходной. Стадии работают конкурентно — пока одна обрабатывает текущий элемент, другая уже работает со следующим. Преимущества: модульность (стадии независимы), конкурентность без явной синхронизации, естественная поддержка backpressure.",[15,4824,4825,4828,4830],{},[1075,4826,4827],{},"Q: Чем fan-out отличается от worker pool?",[4820,4829],{},"\nA: В fan-out каждый воркер получает свой канал — источник данных копируется или распределяется по воркерам, количество воркеров фиксировано при создании. В worker pool все воркеры читают из одного общего канала задач — естественная балансировка нагрузки, медленный воркер просто берёт меньше задач. Worker pool предпочтительнее когда время обработки задач варьируется.",[15,4832,4833,4836,4838,4839,4842,4843,4846,4847,4850,4851,4853],{},[1075,4834,4835],{},"Q: Как реализовать семафор в Go?",[4820,4837],{},"\nA: Через буферизованный канал: ",[37,4840,4841],{},"sem := make(chan struct{}, N)",". Захват: ",[37,4844,4845],{},"sem \u003C- struct{}{}",", освобождение: ",[37,4848,4849],{},"\u003C-sem",". Блокируется когда N слотов заняты. Для production лучше ",[37,4852,3044],{}," с поддержкой контекста и взятия нескольких единиц.",[15,4855,4856,4859,4861,4862,4864,4865,4868,4869,4871,4872,4874],{},[1075,4857,4858],{},"Q: Чем errgroup лучше WaitGroup?",[4820,4860],{},"\nA: ",[37,4863,4796],{}," добавляет обработку ошибок из горутин — ",[37,4866,4867],{},"Wait()"," возвращает первую ошибку. С ",[37,4870,3526],{}," контекст автоматически отменяется при первой ошибке, сигнализируя остальным горутинам. ",[37,4873,3561],{}," (Go 1.20) добавляет встроенный семафор. WaitGroup только ждёт завершения без обработки ошибок.",[15,4876,4877,4880,4882,4883,4885,4886,4888],{},[1075,4878,4879],{},"Q: Почему важно поддерживать контекст в каждой стадии pipeline?",[4820,4881],{},"\nA: Без контекста при отмене потребитель перестаёт читать из канала, и горутины-стадии зависают, заблокированные на отправке — утечка горутин. С ",[37,4884,3530],{}," в каждом ",[37,4887,4175],{}," горутины корректно завершаются при отмене контекста.",[15,4890,4891,4894,4896,4897,4900,4901,4903,4904,4906,4907,4910,4911,4914],{},[1075,4892,4893],{},"Q: Как реализовать retry с экспоненциальным backoff?",[4820,4895],{},"\nA: Цикл на N попыток, между попытками — ",[37,4898,4899],{},"time.After(backoff)"," в ",[37,4902,4175],{}," с ",[37,4905,3530],{},". Backoff удваивается каждую итерацию: ",[37,4908,4909],{},"time.Duration(1\u003C\u003Cuint(i)) * baseDelay",". Обязательно проверять ",[37,4912,4913],{},"ctx.Err()"," чтобы не ретраить при отменённом контексте.",[15,4916,4917,4920,4922,4923,4926,4927,4930],{},[1075,4918,4919],{},"Q: Как безопасно писать результаты из нескольких горутин в один слайс?",[4820,4921],{},"\nA: Если каждая горутина пишет в свой индекс — синхронизация не нужна, горутины не конкурируют за одну ячейку. Именно так работает errgroup с ",[37,4924,4925],{},"results[i] = resp"," — ",[37,4928,4929],{},"i"," уникален для каждой горутины. Если индексы пересекаются — нужен мьютекс или канал результатов.",[15,4932,4933,4936,4938],{},[1075,4934,4935],{},"Q: Что такое backpressure и как pipeline его обеспечивает?",[4820,4937],{},"\nA: Backpressure — механизм при котором быстрый производитель замедляется если потребитель не успевает. В pipeline это происходит автоматически: если стадия N медленная, её входной канал заполнен, предыдущая стадия блокируется на отправке и тоже замедляется. Цепочка блокировок распространяется до источника.",[19,4940],{},[10,4942,4944],{"id":4943},"задачи-паттерны-конкурентности","Задачи: Паттерны конкурентности",[19,4946],{},[15,4948,4949],{},[1075,4950,4951],{},"Задача 1: Merge каналов",[15,4953,4954,4957],{},[1075,4955,4956],{},"Уровень:"," Лёгкая",[15,4959,4960,4963],{},[1075,4961,4962],{},"Что проверяет:"," fan-in, работа с несколькими каналами",[15,4965,4966,4969,4970,4973],{},[1075,4967,4968],{},"Условие:"," Напиши функцию ",[37,4971,4972],{},"merge(channels ...\u003C-chan int) \u003C-chan int"," которая объединяет несколько каналов в один. Результирующий канал закрывается когда все входные каналы закрыты.",[15,4975,4976],{},[1075,4977,4978],{},"Решение:",[30,4980,4982],{"className":32,"code":4981,"language":34,"meta":35,"style":35},"package main\n\nimport (\n    \"fmt\"\n    \"sync\"\n)\n\nfunc merge(channels ...\u003C-chan int) \u003C-chan int {\n    out := make(chan int)\n    var wg sync.WaitGroup\n\n    forward := func(ch \u003C-chan int) {\n        defer wg.Done()\n        for v := range ch {\n            out \u003C- v\n        }\n    }\n\n    wg.Add(len(channels))\n    for _, ch := range channels {\n        go forward(ch)\n    }\n\n    go func() {\n        wg.Wait()\n        close(out)\n    }()\n\n    return out\n}\n\nfunc makeChannel(vals ...int) \u003C-chan int {\n    ch := make(chan int)\n    go func() {\n        defer close(ch)\n        for _, v := range vals {\n            ch \u003C- v\n        }\n    }()\n    return ch\n}\n\nfunc main() {\n    ch1 := makeChannel(1, 2, 3)\n    ch2 := makeChannel(4, 5, 6)\n    ch3 := makeChannel(7, 8, 9)\n\n    for v := range merge(ch1, ch2, ch3) {\n        fmt.Print(v, \" \") \u002F\u002F порядок непредсказуем, но все числа будут\\n    }\n}\n",[37,4983,4984,4992,4996,5003,5013,5021,5025,5029,5052,5068,5080,5084,5102,5112,5125,5134,5138,5142,5146,5158,5170,5178,5182,5186,5194,5202,5208,5212,5216,5222,5226,5230,5253,5270,5278,5286,5300,5309,5313,5317,5324,5328,5332,5340,5363,5386,5409,5413,5428,5446],{"__ignoreMap":35},[40,4985,4986,4989],{"class":42,"line":43},[40,4987,4988],{"class":53},"package",[40,4990,4991],{"class":57}," main\n",[40,4993,4994],{"class":42,"line":50},[40,4995,193],{"emptyLinePlaceholder":192},[40,4997,4998,5000],{"class":42,"line":87},[40,4999,3055],{"class":53},[40,5001,5002],{"class":61}," (\n",[40,5004,5005,5008,5011],{"class":42,"line":109},[40,5006,5007],{"class":2359},"    \"",[40,5009,5010],{"class":57},"fmt",[40,5012,3063],{"class":2359},[40,5014,5015,5017,5019],{"class":42,"line":121},[40,5016,5007],{"class":2359},[40,5018,1418],{"class":57},[40,5020,3063],{"class":2359},[40,5022,5023],{"class":42,"line":133},[40,5024,106],{"class":61},[40,5026,5027],{"class":42,"line":150},[40,5028,193],{"emptyLinePlaceholder":192},[40,5030,5031,5033,5036,5038,5040,5042,5044,5046,5048,5050],{"class":42,"line":162},[40,5032,54],{"class":53},[40,5034,5035],{"class":57}," merge",[40,5037,62],{"class":61},[40,5039,1377],{"class":65},[40,5041,1380],{"class":53},[40,5043,81],{"class":53},[40,5045,75],{"class":61},[40,5047,78],{"class":53},[40,5049,81],{"class":53},[40,5051,84],{"class":61},[40,5053,5054,5056,5058,5060,5062,5064,5066],{"class":42,"line":168},[40,5055,90],{"class":61},[40,5057,93],{"class":53},[40,5059,96],{"class":57},[40,5061,62],{"class":61},[40,5063,101],{"class":53},[40,5065,81],{"class":53},[40,5067,106],{"class":61},[40,5069,5070,5072,5074,5076,5078],{"class":42,"line":174},[40,5071,1412],{"class":53},[40,5073,1415],{"class":61},[40,5075,1418],{"class":57},[40,5077,657],{"class":61},[40,5079,1423],{"class":57},[40,5081,5082],{"class":42,"line":183},[40,5083,193],{"emptyLinePlaceholder":192},[40,5085,5086,5088,5090,5092,5094,5096,5098,5100],{"class":42,"line":189},[40,5087,1437],{"class":61},[40,5089,93],{"class":53},[40,5091,115],{"class":53},[40,5093,62],{"class":61},[40,5095,1446],{"class":65},[40,5097,215],{"class":53},[40,5099,81],{"class":53},[40,5101,1453],{"class":61},[40,5103,5104,5106,5108,5110],{"class":42,"line":196},[40,5105,124],{"class":53},[40,5107,1460],{"class":61},[40,5109,753],{"class":57},[40,5111,978],{"class":61},[40,5113,5114,5116,5119,5121,5123],{"class":42,"line":202},[40,5115,136],{"class":53},[40,5117,5118],{"class":61}," v ",[40,5120,93],{"class":53},[40,5122,144],{"class":53},[40,5124,1477],{"class":61},[40,5126,5127,5129,5131],{"class":42,"line":228},[40,5128,153],{"class":61},[40,5130,156],{"class":53},[40,5132,5133],{"class":61}," v\n",[40,5135,5136],{"class":42,"line":245},[40,5137,165],{"class":61},[40,5139,5140],{"class":42,"line":254},[40,5141,614],{"class":61},[40,5143,5144],{"class":42,"line":263},[40,5145,193],{"emptyLinePlaceholder":192},[40,5147,5148,5150,5152,5154,5156],{"class":42,"line":278},[40,5149,1532],{"class":61},[40,5151,1535],{"class":57},[40,5153,62],{"class":61},[40,5155,1540],{"class":57},[40,5157,1543],{"class":61},[40,5159,5160,5162,5164,5166,5168],{"class":42,"line":292},[40,5161,583],{"class":53},[40,5163,1550],{"class":61},[40,5165,93],{"class":53},[40,5167,144],{"class":53},[40,5169,1557],{"class":61},[40,5171,5172,5174,5176],{"class":42,"line":297},[40,5173,1562],{"class":53},[40,5175,1565],{"class":57},[40,5177,1568],{"class":61},[40,5179,5180],{"class":42,"line":302},[40,5181,614],{"class":61},[40,5183,5184],{"class":42,"line":309},[40,5185,193],{"emptyLinePlaceholder":192},[40,5187,5188,5190,5192],{"class":42,"line":314},[40,5189,112],{"class":53},[40,5191,115],{"class":53},[40,5193,118],{"class":61},[40,5195,5196,5198,5200],{"class":42,"line":319},[40,5197,1595],{"class":61},[40,5199,1598],{"class":57},[40,5201,978],{"class":61},[40,5203,5204,5206],{"class":42,"line":325},[40,5205,1606],{"class":57},[40,5207,130],{"class":61},[40,5209,5210],{"class":42,"line":349},[40,5211,171],{"class":61},[40,5213,5214],{"class":42,"line":366},[40,5215,193],{"emptyLinePlaceholder":192},[40,5217,5218,5220],{"class":42,"line":375},[40,5219,177],{"class":53},[40,5221,180],{"class":61},[40,5223,5224],{"class":42,"line":384},[40,5225,186],{"class":61},[40,5227,5228],{"class":42,"line":397},[40,5229,193],{"emptyLinePlaceholder":192},[40,5231,5232,5234,5237,5239,5242,5245,5247,5249,5251],{"class":42,"line":421},[40,5233,54],{"class":53},[40,5235,5236],{"class":57}," makeChannel",[40,5238,62],{"class":61},[40,5240,5241],{"class":65},"vals",[40,5243,5244],{"class":53}," ...int",[40,5246,75],{"class":61},[40,5248,78],{"class":53},[40,5250,81],{"class":53},[40,5252,84],{"class":61},[40,5254,5255,5258,5260,5262,5264,5266,5268],{"class":42,"line":431},[40,5256,5257],{"class":61},"    ch ",[40,5259,93],{"class":53},[40,5261,96],{"class":57},[40,5263,62],{"class":61},[40,5265,101],{"class":53},[40,5267,81],{"class":53},[40,5269,106],{"class":61},[40,5271,5272,5274,5276],{"class":42,"line":437},[40,5273,112],{"class":53},[40,5275,115],{"class":53},[40,5277,118],{"class":61},[40,5279,5280,5282,5284],{"class":42,"line":442},[40,5281,124],{"class":53},[40,5283,127],{"class":57},[40,5285,1568],{"class":61},[40,5287,5288,5290,5293,5295,5297],{"class":42,"line":447},[40,5289,136],{"class":53},[40,5291,5292],{"class":61}," _, v ",[40,5294,93],{"class":53},[40,5296,144],{"class":53},[40,5298,5299],{"class":61}," vals {\n",[40,5301,5302,5305,5307],{"class":42,"line":454},[40,5303,5304],{"class":61},"            ch ",[40,5306,156],{"class":53},[40,5308,5133],{"class":61},[40,5310,5311],{"class":42,"line":459},[40,5312,165],{"class":61},[40,5314,5315],{"class":42,"line":464},[40,5316,171],{"class":61},[40,5318,5319,5321],{"class":42,"line":474},[40,5320,177],{"class":53},[40,5322,5323],{"class":61}," ch\n",[40,5325,5326],{"class":42,"line":480},[40,5327,186],{"class":61},[40,5329,5330],{"class":42,"line":549},[40,5331,193],{"emptyLinePlaceholder":192},[40,5333,5334,5336,5338],{"class":42,"line":562},[40,5335,54],{"class":53},[40,5337,469],{"class":57},[40,5339,118],{"class":61},[40,5341,5342,5345,5347,5349,5351,5353,5355,5357,5359,5361],{"class":42,"line":575},[40,5343,5344],{"class":61},"    ch1 ",[40,5346,93],{"class":53},[40,5348,5236],{"class":57},[40,5350,62],{"class":61},[40,5352,498],{"class":409},[40,5354,501],{"class":61},[40,5356,410],{"class":409},[40,5358,501],{"class":61},[40,5360,508],{"class":409},[40,5362,106],{"class":61},[40,5364,5365,5368,5370,5372,5374,5376,5378,5380,5382,5384],{"class":42,"line":580},[40,5366,5367],{"class":61},"    ch2 ",[40,5369,93],{"class":53},[40,5371,5236],{"class":57},[40,5373,62],{"class":61},[40,5375,513],{"class":409},[40,5377,501],{"class":61},[40,5379,518],{"class":409},[40,5381,501],{"class":61},[40,5383,523],{"class":409},[40,5385,106],{"class":61},[40,5387,5388,5391,5393,5395,5397,5399,5401,5403,5405,5407],{"class":42,"line":596},[40,5389,5390],{"class":61},"    ch3 ",[40,5392,93],{"class":53},[40,5394,5236],{"class":57},[40,5396,62],{"class":61},[40,5398,528],{"class":409},[40,5400,501],{"class":61},[40,5402,533],{"class":409},[40,5404,501],{"class":61},[40,5406,538],{"class":409},[40,5408,106],{"class":61},[40,5410,5411],{"class":42,"line":611},[40,5412,193],{"emptyLinePlaceholder":192},[40,5414,5415,5417,5419,5421,5423,5425],{"class":42,"line":617},[40,5416,583],{"class":53},[40,5418,5118],{"class":61},[40,5420,93],{"class":53},[40,5422,144],{"class":53},[40,5424,5035],{"class":57},[40,5426,5427],{"class":61},"(ch1, ch2, ch3) {\n",[40,5429,5430,5432,5435,5438,5441,5443],{"class":42,"line":1592},[40,5431,599],{"class":61},[40,5433,5434],{"class":57},"Print",[40,5436,5437],{"class":61},"(v, ",[40,5439,5440],{"class":2359},"\" \"",[40,5442,75],{"class":61},[40,5444,5445],{"class":46},"\u002F\u002F порядок непредсказуем, но все числа будут\\n    }\n",[40,5447,5448],{"class":42,"line":1603},[40,5449,186],{"class":61},[19,5451],{},[15,5453,5454],{},[1075,5455,5456],{},"Задача 2: Retry с errgroup",[15,5458,5459,5461],{},[1075,5460,4956],{}," Средняя",[15,5463,5464,5466],{},[1075,5465,4962],{}," errgroup, обработка ошибок из нескольких горутин",[15,5468,5469,4969,5471,5474,5475,5478],{},[1075,5470,4968],{},[37,5472,5473],{},"fetchAll(ctx context.Context, urls []string) ([]string, error)"," которая конкурентно загружает все URL (имитируй через ",[37,5476,5477],{},"time.Sleep"," + возврат строки), но не более 3 одновременно. При ошибке хотя бы одного — отменяет остальные и возвращает ошибку.",[15,5480,5481],{},[1075,5482,4978],{},[30,5484,5486],{"className":32,"code":5485,"language":34,"meta":35,"style":35},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n\n    \"golang.org\u002Fx\u002Fsync\u002Ferrgroup\"\n)\n\n\u002F\u002F Имитация загрузки URL\nfunc fetchURL(ctx context.Context, url string) (string, error) {\n    select {\n    case \u003C-time.After(100 * time.Millisecond):\n        return \"content of \" + url, nil\n    case \u003C-ctx.Done():\n        return \"\", ctx.Err()\n    }\n}\n\nfunc fetchAll(ctx context.Context, urls []string) ([]string, error) {\n    g, ctx := errgroup.WithContext(ctx)\n    g.SetLimit(3) \u002F\u002F не более 3 одновременно\n\n    results := make([]string, len(urls))\n\n    for i, url := range urls {\n        i, url := i, url\n        g.Go(func() error {\n            content, err := fetchURL(ctx, url)\n            if err != nil {\n                return fmt.Errorf(\"fetch %s: %w\", url, err)\n            }\n            results[i] = content\n            return nil\n        })\n    }\n\n    if err := g.Wait(); err != nil {\n        return nil, err\n    }\n\n    return results, nil\n}\n\nfunc main() {\n    ctx := context.Background()\n    urls := []string{\n        \"https:\u002F\u002Fexample.com\u002F1\",\n        \"https:\u002F\u002Fexample.com\u002F2\",\n        \"https:\u002F\u002Fexample.com\u002F3\",\n        \"https:\u002F\u002Fexample.com\u002F4\",\n        \"https:\u002F\u002Fexample.com\u002F5\",\n    }\n\n    results, err := fetchAll(ctx, urls)\n    if err != nil {\n        fmt.Println(\"error:\", err)\\n        return\n    }\n\n    for _, r := range results {\n        fmt.Println(r)\n    }\n}\n",[37,5487,5488,5494,5498,5504,5513,5521,5530,5534,5542,5546,5550,5555,5588,5594,5613,5628,5640,5654,5658,5662,5666,5700,5712,5727,5731,5749,5753,5765,5774,5790,5801,5813,5835,5839,5848,5854,5858,5862,5866,5886,5894,5898,5902,5910,5914,5918,5926,5939,5952,5959,5966,5973,5980,5987,5991,5995,6007,6019,6036,6040,6044,6057,6066,6070],{"__ignoreMap":35},[40,5489,5490,5492],{"class":42,"line":43},[40,5491,4988],{"class":53},[40,5493,4991],{"class":57},[40,5495,5496],{"class":42,"line":50},[40,5497,193],{"emptyLinePlaceholder":192},[40,5499,5500,5502],{"class":42,"line":87},[40,5501,3055],{"class":53},[40,5503,5002],{"class":61},[40,5505,5506,5508,5511],{"class":42,"line":109},[40,5507,5007],{"class":2359},[40,5509,5510],{"class":57},"context",[40,5512,3063],{"class":2359},[40,5514,5515,5517,5519],{"class":42,"line":121},[40,5516,5007],{"class":2359},[40,5518,5010],{"class":57},[40,5520,3063],{"class":2359},[40,5522,5523,5525,5528],{"class":42,"line":133},[40,5524,5007],{"class":2359},[40,5526,5527],{"class":57},"time",[40,5529,3063],{"class":2359},[40,5531,5532],{"class":42,"line":150},[40,5533,193],{"emptyLinePlaceholder":192},[40,5535,5536,5538,5540],{"class":42,"line":162},[40,5537,5007],{"class":2359},[40,5539,3230],{"class":57},[40,5541,3063],{"class":2359},[40,5543,5544],{"class":42,"line":168},[40,5545,106],{"class":61},[40,5547,5548],{"class":42,"line":174},[40,5549,193],{"emptyLinePlaceholder":192},[40,5551,5552],{"class":42,"line":183},[40,5553,5554],{"class":46},"\u002F\u002F Имитация загрузки URL\n",[40,5556,5557,5559,5561,5563,5565,5567,5569,5571,5573,5576,5578,5580,5582,5584,5586],{"class":42,"line":189},[40,5558,54],{"class":53},[40,5560,3381],{"class":57},[40,5562,62],{"class":61},[40,5564,651],{"class":65},[40,5566,654],{"class":57},[40,5568,657],{"class":61},[40,5570,660],{"class":57},[40,5572,501],{"class":61},[40,5574,5575],{"class":65},"url",[40,5577,2962],{"class":53},[40,5579,4448],{"class":61},[40,5581,3277],{"class":53},[40,5583,501],{"class":61},[40,5585,2634],{"class":53},[40,5587,1453],{"class":61},[40,5589,5590,5592],{"class":42,"line":196},[40,5591,2641],{"class":53},[40,5593,84],{"class":61},[40,5595,5596,5598,5600,5602,5604,5606,5608,5610],{"class":42,"line":202},[40,5597,2648],{"class":53},[40,5599,747],{"class":53},[40,5601,3992],{"class":61},[40,5603,3995],{"class":57},[40,5605,62],{"class":61},[40,5607,2296],{"class":409},[40,5609,3972],{"class":53},[40,5611,5612],{"class":61}," time.Millisecond):\n",[40,5614,5615,5617,5620,5623,5626],{"class":42,"line":228},[40,5616,2678],{"class":53},[40,5618,5619],{"class":2359}," \"content of \"",[40,5621,5622],{"class":53}," +",[40,5624,5625],{"class":61}," url, ",[40,5627,3517],{"class":409},[40,5629,5630,5632,5634,5636,5638],{"class":42,"line":245},[40,5631,2648],{"class":53},[40,5633,747],{"class":53},[40,5635,750],{"class":61},[40,5637,753],{"class":57},[40,5639,900],{"class":61},[40,5641,5642,5644,5647,5650,5652],{"class":42,"line":254},[40,5643,2678],{"class":53},[40,5645,5646],{"class":2359}," \"\"",[40,5648,5649],{"class":61},", ctx.",[40,5651,2684],{"class":57},[40,5653,978],{"class":61},[40,5655,5656],{"class":42,"line":263},[40,5657,614],{"class":61},[40,5659,5660],{"class":42,"line":278},[40,5661,186],{"class":61},[40,5663,5664],{"class":42,"line":292},[40,5665,193],{"emptyLinePlaceholder":192},[40,5667,5668,5670,5672,5674,5676,5678,5680,5682,5684,5686,5688,5690,5692,5694,5696,5698],{"class":42,"line":297},[40,5669,54],{"class":53},[40,5671,3257],{"class":57},[40,5673,62],{"class":61},[40,5675,651],{"class":65},[40,5677,654],{"class":57},[40,5679,657],{"class":61},[40,5681,660],{"class":57},[40,5683,501],{"class":61},[40,5685,3272],{"class":65},[40,5687,69],{"class":61},[40,5689,3277],{"class":53},[40,5691,3280],{"class":61},[40,5693,3277],{"class":53},[40,5695,501],{"class":61},[40,5697,2634],{"class":53},[40,5699,1453],{"class":61},[40,5701,5702,5704,5706,5708,5710],{"class":42,"line":302},[40,5703,3293],{"class":61},[40,5705,93],{"class":53},[40,5707,3298],{"class":61},[40,5709,3301],{"class":57},[40,5711,3304],{"class":61},[40,5713,5714,5716,5718,5720,5722,5724],{"class":42,"line":309},[40,5715,3603],{"class":61},[40,5717,3561],{"class":57},[40,5719,62],{"class":61},[40,5721,508],{"class":409},[40,5723,75],{"class":61},[40,5725,5726],{"class":46},"\u002F\u002F не более 3 одновременно\n",[40,5728,5729],{"class":42,"line":314},[40,5730,193],{"emptyLinePlaceholder":192},[40,5732,5733,5735,5737,5739,5741,5743,5745,5747],{"class":42,"line":319},[40,5734,1753],{"class":61},[40,5736,93],{"class":53},[40,5738,96],{"class":57},[40,5740,490],{"class":61},[40,5742,3277],{"class":53},[40,5744,501],{"class":61},[40,5746,1540],{"class":57},[40,5748,3323],{"class":61},[40,5750,5751],{"class":42,"line":325},[40,5752,193],{"emptyLinePlaceholder":192},[40,5754,5755,5757,5759,5761,5763],{"class":42,"line":349},[40,5756,583],{"class":53},[40,5758,3334],{"class":61},[40,5760,93],{"class":53},[40,5762,144],{"class":53},[40,5764,2936],{"class":61},[40,5766,5767,5769,5771],{"class":42,"line":366},[40,5768,3345],{"class":61},[40,5770,93],{"class":53},[40,5772,5773],{"class":61}," i, url\n",[40,5775,5776,5778,5780,5782,5784,5786,5788],{"class":42,"line":375},[40,5777,3357],{"class":61},[40,5779,3360],{"class":57},[40,5781,62],{"class":61},[40,5783,54],{"class":53},[40,5785,3367],{"class":61},[40,5787,2634],{"class":53},[40,5789,84],{"class":61},[40,5791,5792,5795,5797,5799],{"class":42,"line":384},[40,5793,5794],{"class":61},"            content, err ",[40,5796,93],{"class":53},[40,5798,3381],{"class":57},[40,5800,3384],{"class":61},[40,5802,5803,5805,5807,5809,5811],{"class":42,"line":397},[40,5804,400],{"class":53},[40,5806,2816],{"class":61},[40,5808,2438],{"class":53},[40,5810,2441],{"class":409},[40,5812,84],{"class":61},[40,5814,5815,5817,5819,5821,5823,5825,5827,5829,5831,5833],{"class":42,"line":421},[40,5816,3401],{"class":53},[40,5818,3404],{"class":61},[40,5820,3407],{"class":57},[40,5822,62],{"class":61},[40,5824,3412],{"class":2359},[40,5826,3415],{"class":409},[40,5828,3418],{"class":2359},[40,5830,3421],{"class":409},[40,5832,2366],{"class":2359},[40,5834,3426],{"class":61},[40,5836,5837],{"class":42,"line":431},[40,5838,434],{"class":61},[40,5840,5841,5843,5845],{"class":42,"line":437},[40,5842,3435],{"class":61},[40,5844,1186],{"class":53},[40,5846,5847],{"class":61}," content\n",[40,5849,5850,5852],{"class":42,"line":442},[40,5851,3448],{"class":53},[40,5853,2741],{"class":409},[40,5855,5856],{"class":42,"line":447},[40,5857,3455],{"class":61},[40,5859,5860],{"class":42,"line":454},[40,5861,614],{"class":61},[40,5863,5864],{"class":42,"line":459},[40,5865,193],{"emptyLinePlaceholder":192},[40,5867,5868,5870,5872,5874,5876,5878,5880,5882,5884],{"class":42,"line":464},[40,5869,2813],{"class":53},[40,5871,2816],{"class":61},[40,5873,93],{"class":53},[40,5875,3479],{"class":61},[40,5877,1598],{"class":57},[40,5879,3484],{"class":61},[40,5881,2438],{"class":53},[40,5883,2441],{"class":409},[40,5885,84],{"class":61},[40,5887,5888,5890,5892],{"class":42,"line":474},[40,5889,2678],{"class":53},[40,5891,2441],{"class":409},[40,5893,3499],{"class":61},[40,5895,5896],{"class":42,"line":480},[40,5897,614],{"class":61},[40,5899,5900],{"class":42,"line":549},[40,5901,193],{"emptyLinePlaceholder":192},[40,5903,5904,5906,5908],{"class":42,"line":562},[40,5905,177],{"class":53},[40,5907,3514],{"class":61},[40,5909,3517],{"class":409},[40,5911,5912],{"class":42,"line":575},[40,5913,186],{"class":61},[40,5915,5916],{"class":42,"line":580},[40,5917,193],{"emptyLinePlaceholder":192},[40,5919,5920,5922,5924],{"class":42,"line":596},[40,5921,54],{"class":53},[40,5923,469],{"class":57},[40,5925,118],{"class":61},[40,5927,5928,5931,5933,5935,5937],{"class":42,"line":611},[40,5929,5930],{"class":61},"    ctx ",[40,5932,93],{"class":53},[40,5934,948],{"class":61},[40,5936,957],{"class":57},[40,5938,978],{"class":61},[40,5940,5941,5944,5946,5948,5950],{"class":42,"line":617},[40,5942,5943],{"class":61},"    urls ",[40,5945,93],{"class":53},[40,5947,69],{"class":61},[40,5949,3277],{"class":53},[40,5951,2120],{"class":61},[40,5953,5954,5957],{"class":42,"line":1592},[40,5955,5956],{"class":2359},"        \"https:\u002F\u002Fexample.com\u002F1\"",[40,5958,1920],{"class":61},[40,5960,5961,5964],{"class":42,"line":1603},[40,5962,5963],{"class":2359},"        \"https:\u002F\u002Fexample.com\u002F2\"",[40,5965,1920],{"class":61},[40,5967,5968,5971],{"class":42,"line":1612},[40,5969,5970],{"class":2359},"        \"https:\u002F\u002Fexample.com\u002F3\"",[40,5972,1920],{"class":61},[40,5974,5975,5978],{"class":42,"line":1617},[40,5976,5977],{"class":2359},"        \"https:\u002F\u002Fexample.com\u002F4\"",[40,5979,1920],{"class":61},[40,5981,5982,5985],{"class":42,"line":1622},[40,5983,5984],{"class":2359},"        \"https:\u002F\u002Fexample.com\u002F5\"",[40,5986,1920],{"class":61},[40,5988,5989],{"class":42,"line":1630},[40,5990,614],{"class":61},[40,5992,5993],{"class":42,"line":1635},[40,5994,193],{"emptyLinePlaceholder":192},[40,5996,5997,6000,6002,6004],{"class":42,"line":1640},[40,5998,5999],{"class":61},"    results, err ",[40,6001,93],{"class":53},[40,6003,3257],{"class":57},[40,6005,6006],{"class":61},"(ctx, urls)\n",[40,6008,6009,6011,6013,6015,6017],{"class":42,"line":1646},[40,6010,2813],{"class":53},[40,6012,2816],{"class":61},[40,6014,2438],{"class":53},[40,6016,2441],{"class":409},[40,6018,84],{"class":61},[40,6020,6021,6023,6025,6027,6030,6033],{"class":42,"line":1655},[40,6022,599],{"class":61},[40,6024,602],{"class":57},[40,6026,62],{"class":61},[40,6028,6029],{"class":2359},"\"error:\"",[40,6031,6032],{"class":61},", err)\\n        ",[40,6034,6035],{"class":53},"return\n",[40,6037,6038],{"class":42,"line":1678},[40,6039,614],{"class":61},[40,6041,6042],{"class":42,"line":1687},[40,6043,193],{"emptyLinePlaceholder":192},[40,6045,6046,6048,6051,6053,6055],{"class":42,"line":1692},[40,6047,583],{"class":53},[40,6049,6050],{"class":61}," _, r ",[40,6052,93],{"class":53},[40,6054,144],{"class":53},[40,6056,1784],{"class":61},[40,6058,6059,6061,6063],{"class":42,"line":1711},[40,6060,599],{"class":61},[40,6062,602],{"class":57},[40,6064,6065],{"class":61},"(r)\n",[40,6067,6068],{"class":42,"line":1716},[40,6069,614],{"class":61},[40,6071,6072],{"class":42,"line":1722},[40,6073,186],{"class":61},[19,6075],{},[15,6077,6078],{},[1075,6079,6080],{},"Задача 3: Rate limiter",[15,6082,6083,6085],{},[1075,6084,4956],{}," Сложная",[15,6087,6088,6090],{},[1075,6089,4962],{}," комбинирование паттернов, ticker, семафор",[15,6092,6093,6095,6096,6099,6100,6103],{},[1075,6094,4968],{}," Реализуй ",[37,6097,6098],{},"RateLimiter"," который позволяет не более N запросов в секунду. Метод ",[37,6101,6102],{},"Allow(ctx context.Context) error"," блокируется если лимит исчерпан, и возвращает nil когда можно выполнить запрос. Если контекст отменён — возвращает ошибку.",[15,6105,6106,6109],{},[1075,6107,6108],{},"Подсказка:"," Используй буферизованный канал как токен-bucket. Ticker пополняет токены с нужной частотой.",[15,6111,6112],{},[1075,6113,4978],{},[30,6115,6117],{"className":32,"code":6116,"language":34,"meta":35,"style":35},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n)\n\ntype RateLimiter struct {\n    tokens chan struct{}\n}\n\nfunc NewRateLimiter(rps int) *RateLimiter {\n    rl := &RateLimiter{\n        tokens: make(chan struct{}, rps),\n    }\n\n    \u002F\u002F Заполняем начальные токены\n    for i := 0; i \u003C rps; i++ {\n        rl.tokens \u003C- struct{}{}\n    }\n\n    \u002F\u002F Пополняем токены с частотой rps\u002Fсек\n    go func() {\n        ticker := time.NewTicker(time.Second \u002F time.Duration(rps))\n        defer ticker.Stop()\n        for range ticker.C {\n            select {\n            case rl.tokens \u003C- struct{}{}: \u002F\u002F добавляем токен если есть место\n            default:                      \u002F\u002F буфер полон — пропускаем\n            }\n        }\n    }()\n\n    return rl\n}\n\nfunc (rl *RateLimiter) Allow(ctx context.Context) error {\n    select {\n    case \u003C-rl.tokens: \u002F\u002F берём токен\n        return nil\n    case \u003C-ctx.Done():\n        return ctx.Err()\n    }\n}\n\nfunc main() {\n    limiter := NewRateLimiter(3) \u002F\u002F 3 запроса в секунду\n    ctx := context.Background()\n\n    for i := 0; i \u003C 9; i++ {\n        if err := limiter.Allow(ctx); err != nil {\n            fmt.Println(\"cancelled:\", err)\\n            return\n        }\n        fmt.Printf(\"запрос %d выполнен в %s\\n\", i+1,\n            time.Now().Format(\"15:04:05.000\"))\n    }\n}\n\n\u002F\u002F Первые 3 выполняются сразу (начальные токены).\n\u002F\u002F Следующие — по 3 в секунду.\n",[37,6118,6119,6125,6129,6135,6143,6151,6159,6163,6167,6178,6189,6193,6197,6219,6232,6248,6252,6256,6261,6282,6294,6298,6302,6307,6315,6340,6352,6361,6367,6383,6394,6398,6402,6406,6410,6417,6421,6425,6459,6465,6477,6483,6495,6505,6509,6513,6517,6525,6543,6555,6559,6582,6604,6621,6625,6657,6679,6683,6687,6691,6696],{"__ignoreMap":35},[40,6120,6121,6123],{"class":42,"line":43},[40,6122,4988],{"class":53},[40,6124,4991],{"class":57},[40,6126,6127],{"class":42,"line":50},[40,6128,193],{"emptyLinePlaceholder":192},[40,6130,6131,6133],{"class":42,"line":87},[40,6132,3055],{"class":53},[40,6134,5002],{"class":61},[40,6136,6137,6139,6141],{"class":42,"line":109},[40,6138,5007],{"class":2359},[40,6140,5510],{"class":57},[40,6142,3063],{"class":2359},[40,6144,6145,6147,6149],{"class":42,"line":121},[40,6146,5007],{"class":2359},[40,6148,5010],{"class":57},[40,6150,3063],{"class":2359},[40,6152,6153,6155,6157],{"class":42,"line":133},[40,6154,5007],{"class":2359},[40,6156,5527],{"class":57},[40,6158,3063],{"class":2359},[40,6160,6161],{"class":42,"line":150},[40,6162,106],{"class":61},[40,6164,6165],{"class":42,"line":162},[40,6166,193],{"emptyLinePlaceholder":192},[40,6168,6169,6171,6174,6176],{"class":42,"line":168},[40,6170,1825],{"class":53},[40,6172,6173],{"class":57}," RateLimiter",[40,6175,1830],{"class":53},[40,6177,84],{"class":61},[40,6179,6180,6183,6185,6187],{"class":42,"line":174},[40,6181,6182],{"class":61},"    tokens ",[40,6184,101],{"class":53},[40,6186,1830],{"class":53},[40,6188,2521],{"class":61},[40,6190,6191],{"class":42,"line":183},[40,6192,186],{"class":61},[40,6194,6195],{"class":42,"line":189},[40,6196,193],{"emptyLinePlaceholder":192},[40,6198,6199,6201,6204,6206,6209,6211,6213,6215,6217],{"class":42,"line":196},[40,6200,54],{"class":53},[40,6202,6203],{"class":57}," NewRateLimiter",[40,6205,62],{"class":61},[40,6207,6208],{"class":65},"rps",[40,6210,81],{"class":53},[40,6212,75],{"class":61},[40,6214,287],{"class":53},[40,6216,6098],{"class":57},[40,6218,84],{"class":61},[40,6220,6221,6224,6226,6228,6230],{"class":42,"line":202},[40,6222,6223],{"class":61},"    rl ",[40,6225,93],{"class":53},[40,6227,2559],{"class":53},[40,6229,6098],{"class":57},[40,6231,2120],{"class":61},[40,6233,6234,6237,6239,6241,6243,6245],{"class":42,"line":228},[40,6235,6236],{"class":61},"        tokens: ",[40,6238,2571],{"class":57},[40,6240,62],{"class":61},[40,6242,101],{"class":53},[40,6244,1830],{"class":53},[40,6246,6247],{"class":61},"{}, rps),\n",[40,6249,6250],{"class":42,"line":245},[40,6251,614],{"class":61},[40,6253,6254],{"class":42,"line":254},[40,6255,193],{"emptyLinePlaceholder":192},[40,6257,6258],{"class":42,"line":263},[40,6259,6260],{"class":46},"    \u002F\u002F Заполняем начальные токены\n",[40,6262,6263,6265,6267,6269,6271,6273,6275,6278,6280],{"class":42,"line":278},[40,6264,583],{"class":53},[40,6266,1160],{"class":61},[40,6268,93],{"class":53},[40,6270,416],{"class":409},[40,6272,1167],{"class":61},[40,6274,1170],{"class":53},[40,6276,6277],{"class":61}," rps; i",[40,6279,1176],{"class":53},[40,6281,84],{"class":61},[40,6283,6284,6287,6289,6291],{"class":42,"line":292},[40,6285,6286],{"class":61},"        rl.tokens ",[40,6288,156],{"class":53},[40,6290,1830],{"class":53},[40,6292,6293],{"class":61},"{}{}\n",[40,6295,6296],{"class":42,"line":297},[40,6297,614],{"class":61},[40,6299,6300],{"class":42,"line":302},[40,6301,193],{"emptyLinePlaceholder":192},[40,6303,6304],{"class":42,"line":309},[40,6305,6306],{"class":46},"    \u002F\u002F Пополняем токены с частотой rps\u002Fсек\n",[40,6308,6309,6311,6313],{"class":42,"line":314},[40,6310,112],{"class":53},[40,6312,115],{"class":53},[40,6314,118],{"class":61},[40,6316,6317,6320,6322,6324,6327,6330,6333,6335,6337],{"class":42,"line":319},[40,6318,6319],{"class":61},"        ticker ",[40,6321,93],{"class":53},[40,6323,3952],{"class":61},[40,6325,6326],{"class":57},"NewTicker",[40,6328,6329],{"class":61},"(time.Second ",[40,6331,6332],{"class":53},"\u002F",[40,6334,3952],{"class":61},[40,6336,3727],{"class":57},[40,6338,6339],{"class":61},"(rps))\n",[40,6341,6342,6344,6347,6350],{"class":42,"line":325},[40,6343,124],{"class":53},[40,6345,6346],{"class":61}," ticker.",[40,6348,6349],{"class":57},"Stop",[40,6351,978],{"class":61},[40,6353,6354,6356,6358],{"class":42,"line":349},[40,6355,136],{"class":53},[40,6357,144],{"class":53},[40,6359,6360],{"class":61}," ticker.C {\n",[40,6362,6363,6365],{"class":42,"line":366},[40,6364,725],{"class":53},[40,6366,84],{"class":61},[40,6368,6369,6371,6374,6376,6378,6380],{"class":42,"line":375},[40,6370,732],{"class":53},[40,6372,6373],{"class":61}," rl.tokens ",[40,6375,156],{"class":53},[40,6377,1830],{"class":53},[40,6379,2658],{"class":61},[40,6381,6382],{"class":46},"\u002F\u002F добавляем токен если есть место\n",[40,6384,6385,6388,6391],{"class":42,"line":384},[40,6386,6387],{"class":53},"            default",[40,6389,6390],{"class":61},":                      ",[40,6392,6393],{"class":46},"\u002F\u002F буфер полон — пропускаем\n",[40,6395,6396],{"class":42,"line":397},[40,6397,434],{"class":61},[40,6399,6400],{"class":42,"line":421},[40,6401,165],{"class":61},[40,6403,6404],{"class":42,"line":431},[40,6405,171],{"class":61},[40,6407,6408],{"class":42,"line":437},[40,6409,193],{"emptyLinePlaceholder":192},[40,6411,6412,6414],{"class":42,"line":442},[40,6413,177],{"class":53},[40,6415,6416],{"class":61}," rl\n",[40,6418,6419],{"class":42,"line":447},[40,6420,186],{"class":61},[40,6422,6423],{"class":42,"line":454},[40,6424,193],{"emptyLinePlaceholder":192},[40,6426,6427,6429,6431,6434,6436,6438,6440,6443,6445,6447,6449,6451,6453,6455,6457],{"class":42,"line":459},[40,6428,54],{"class":53},[40,6430,2599],{"class":61},[40,6432,6433],{"class":65},"rl ",[40,6435,287],{"class":53},[40,6437,6098],{"class":57},[40,6439,75],{"class":61},[40,6441,6442],{"class":57},"Allow",[40,6444,62],{"class":61},[40,6446,651],{"class":65},[40,6448,654],{"class":57},[40,6450,657],{"class":61},[40,6452,660],{"class":57},[40,6454,75],{"class":61},[40,6456,2634],{"class":53},[40,6458,84],{"class":61},[40,6460,6461,6463],{"class":42,"line":464},[40,6462,2641],{"class":53},[40,6464,84],{"class":61},[40,6466,6467,6469,6471,6474],{"class":42,"line":474},[40,6468,2648],{"class":53},[40,6470,747],{"class":53},[40,6472,6473],{"class":61},"rl.tokens: ",[40,6475,6476],{"class":46},"\u002F\u002F берём токен\n",[40,6478,6479,6481],{"class":42,"line":480},[40,6480,2678],{"class":53},[40,6482,2741],{"class":409},[40,6484,6485,6487,6489,6491,6493],{"class":42,"line":549},[40,6486,2648],{"class":53},[40,6488,747],{"class":53},[40,6490,750],{"class":61},[40,6492,753],{"class":57},[40,6494,900],{"class":61},[40,6496,6497,6499,6501,6503],{"class":42,"line":562},[40,6498,2678],{"class":53},[40,6500,2681],{"class":61},[40,6502,2684],{"class":57},[40,6504,978],{"class":61},[40,6506,6507],{"class":42,"line":575},[40,6508,614],{"class":61},[40,6510,6511],{"class":42,"line":580},[40,6512,186],{"class":61},[40,6514,6515],{"class":42,"line":596},[40,6516,193],{"emptyLinePlaceholder":192},[40,6518,6519,6521,6523],{"class":42,"line":611},[40,6520,54],{"class":53},[40,6522,469],{"class":57},[40,6524,118],{"class":61},[40,6526,6527,6530,6532,6534,6536,6538,6540],{"class":42,"line":617},[40,6528,6529],{"class":61},"    limiter ",[40,6531,93],{"class":53},[40,6533,6203],{"class":57},[40,6535,62],{"class":61},[40,6537,508],{"class":409},[40,6539,75],{"class":61},[40,6541,6542],{"class":46},"\u002F\u002F 3 запроса в секунду\n",[40,6544,6545,6547,6549,6551,6553],{"class":42,"line":1592},[40,6546,5930],{"class":61},[40,6548,93],{"class":53},[40,6550,948],{"class":61},[40,6552,957],{"class":57},[40,6554,978],{"class":61},[40,6556,6557],{"class":42,"line":1603},[40,6558,193],{"emptyLinePlaceholder":192},[40,6560,6561,6563,6565,6567,6569,6571,6573,6576,6578,6580],{"class":42,"line":1612},[40,6562,583],{"class":53},[40,6564,1160],{"class":61},[40,6566,93],{"class":53},[40,6568,416],{"class":409},[40,6570,1167],{"class":61},[40,6572,1170],{"class":53},[40,6574,6575],{"class":409}," 9",[40,6577,2335],{"class":61},[40,6579,1176],{"class":53},[40,6581,84],{"class":61},[40,6583,6584,6586,6588,6590,6593,6595,6598,6600,6602],{"class":42,"line":1617},[40,6585,2432],{"class":53},[40,6587,2816],{"class":61},[40,6589,93],{"class":53},[40,6591,6592],{"class":61}," limiter.",[40,6594,6442],{"class":57},[40,6596,6597],{"class":61},"(ctx); err ",[40,6599,2438],{"class":53},[40,6601,2441],{"class":409},[40,6603,84],{"class":61},[40,6605,6606,6609,6611,6613,6616,6619],{"class":42,"line":1622},[40,6607,6608],{"class":61},"            fmt.",[40,6610,602],{"class":57},[40,6612,62],{"class":61},[40,6614,6615],{"class":2359},"\"cancelled:\"",[40,6617,6618],{"class":61},", err)\\n            ",[40,6620,6035],{"class":53},[40,6622,6623],{"class":42,"line":1630},[40,6624,165],{"class":61},[40,6626,6627,6629,6632,6634,6637,6639,6642,6645,6647,6650,6653,6655],{"class":42,"line":1635},[40,6628,599],{"class":61},[40,6630,6631],{"class":57},"Printf",[40,6633,62],{"class":61},[40,6635,6636],{"class":2359},"\"запрос ",[40,6638,2363],{"class":409},[40,6640,6641],{"class":2359}," выполнен в ",[40,6643,6644],{"class":409},"%s\\n",[40,6646,2366],{"class":2359},[40,6648,6649],{"class":61},", i",[40,6651,6652],{"class":53},"+",[40,6654,498],{"class":409},[40,6656,1920],{"class":61},[40,6658,6659,6662,6665,6668,6671,6673,6676],{"class":42,"line":1640},[40,6660,6661],{"class":61},"            time.",[40,6663,6664],{"class":57},"Now",[40,6666,6667],{"class":61},"().",[40,6669,6670],{"class":57},"Format",[40,6672,62],{"class":61},[40,6674,6675],{"class":2359},"\"15:04:05.000\"",[40,6677,6678],{"class":61},"))\n",[40,6680,6681],{"class":42,"line":1646},[40,6682,614],{"class":61},[40,6684,6685],{"class":42,"line":1655},[40,6686,186],{"class":61},[40,6688,6689],{"class":42,"line":1678},[40,6690,193],{"emptyLinePlaceholder":192},[40,6692,6693],{"class":42,"line":1687},[40,6694,6695],{"class":46},"\u002F\u002F Первые 3 выполняются сразу (начальные токены).\n",[40,6697,6698],{"class":42,"line":1692},[40,6699,6700],{"class":46},"\u002F\u002F Следующие — по 3 в секунду.\n",[19,6702],{},[10,6704,6706],{"id":6705},"викторина-какой-паттерн-выбрать","Викторина: какой паттерн выбрать?",[15,6708,6709],{},"Для каждой ситуации выбери паттерн и объясни почему именно он.",[19,6711],{},[15,6713,6714],{},[1075,6715,6716],{},"Ситуация 1",[15,6718,6719],{},"Нужно скачать 1000 изображений с внешнего сервиса. Сервис не выдерживает больше 10 одновременных запросов.",[15,6721,6722,6725],{},[1075,6723,6724],{},"Ответ:"," Worker Pool или Semaphore.",[30,6727,6732],{"className":6728,"code":6730,"language":6731},[6729],"language-text","Semaphore: ограничиваем конкурентность до 10.\nЗапускаем горутину на каждое изображение, но одновременно\nработают не более 10. Проще реализовать если не нужна\nочередь и переиспользование воркеров.\n\nWorker Pool: если изображений постоянный поток (стрим),\nа не фиксированный список — pool эффективнее: воркеры\nживут долго и берут задачи из общей очереди.\n","text",[37,6733,6730],{"__ignoreMap":35},[19,6735],{},[15,6737,6738],{},[1075,6739,6740],{},"Ситуация 2",[15,6742,6743],{},"Нужно читать строки из файла, парсить каждую как JSON, валидировать и сохранять в базу данных. Чтение быстрое, парсинг медленный, запись в БД ещё медленнее.",[15,6745,6746,6748],{},[1075,6747,6724],{}," Pipeline.",[30,6750,6753],{"className":6751,"code":6752,"language":6731},[6729],"Три стадии с разной скоростью — классический Pipeline.\nСтадия 1: читаем строки → канал.\nСтадия 2: парсим JSON конкурентно (fan-out на несколько парсеров).\nСтадия 3: пишем в БД (с ограничением через semaphore).\nКаждая стадия работает независимо, backpressure возникает\nавтоматически через каналы.\n",[37,6754,6752],{"__ignoreMap":35},[19,6756],{},[15,6758,6759],{},[1075,6760,6761],{},"Ситуация 3",[15,6763,6764],{},"Сервис обращается к трём источникам данных (Redis, PostgreSQL, внешний API) и возвращает агрегированный результат. Все три запроса независимы.",[15,6766,6767,6769],{},[1075,6768,6724],{}," errgroup (Fan-out + сбор результатов).",[30,6771,6774],{"className":6772,"code":6773,"language":6731},[6729],"Три независимых запроса — запускаем параллельно через errgroup.\nПри ошибке любого — контекст отменяется, остальные прерываются.\nWait() возвращает первую ошибку.\nРезультаты пишем в индексированный слайс — без мьютекса,\nкаждая горутина пишет в свой индекс.\n",[37,6775,6773],{"__ignoreMap":35},[19,6777],{},[15,6779,6780],{},[1075,6781,6782],{},"Ситуация 4",[15,6784,6785],{},"Нужно отправить уведомление через три канала (email, SMS, push). Достаточно чтобы хотя бы один канал сработал успешно — остальные можно отменить.",[15,6787,6788,6790],{},[1075,6789,6724],{}," Fan-out + first-result-wins через контекст.",[30,6792,6795],{"className":6793,"code":6794,"language":6731},[6729],"Запускаем три горутины параллельно.\nПервая успешная пишет в буферизованный канал.\nSelect читает первый результат и вызывает cancel() —\nостальные горутины получают ctx.Done() и завершаются.\nПаттерн \"fastest\" из задачи на Context.\n",[37,6796,6794],{"__ignoreMap":35},[19,6798],{},[15,6800,6801],{},[1075,6802,6803],{},"Ситуация 5",[15,6805,6806],{},"Нужно агрегировать метрики из 50 микросервисов каждые 30 секунд. Каждый опрос занимает до 2 секунд.",[15,6808,6809,6811],{},[1075,6810,6724],{}," Fan-out + Fan-in с таймаутом.",[30,6813,6816],{"className":6814,"code":6815,"language":6731},[6729],"Fan-out: запускаем 50 горутин параллельно.\nFan-in: собираем результаты в один канал.\nContext с таймаутом 2 секунды — медленные сервисы\nне задерживают агрегацию.\nЧастичный результат (только те кто успел) лучше\nчем ждать всех.\n",[37,6817,6815],{"__ignoreMap":35},[19,6819],{},[15,6821,6822],{},[1075,6823,6824],{},"Ситуация 6",[15,6826,6827],{},"Нужно обрабатывать события из Kafka. Каждое событие независимо. Хочется максимальной пропускной способности но не потерять события при завершении.",[15,6829,6830,6832],{},[1075,6831,6724],{}," Worker Pool с graceful shutdown.",[30,6834,6837],{"className":6835,"code":6836,"language":6731},[6729],"Worker Pool: фиксированное количество воркеров читают\nиз общей очереди (канала куда пишет Kafka-консьюмер).\nПри завершении: закрываем канал задач, воркеры\nдочитывают оставшиеся события и завершаются.\nWaitGroup ждёт всех воркеров перед остановкой процесса.\n",[37,6838,6836],{"__ignoreMap":35},[19,6840],{},[15,6842,6843],{},[1075,6844,6845],{},"Ситуация 7",[15,6847,6848],{},"Нужно логировать каждый запрос к API но запись в файл медленная и не должна блокировать обработчик.",[15,6850,6851,6853],{},[1075,6852,6724],{}," Буферизованный канал как асинхронная очередь.",[30,6855,6858],{"className":6856,"code":6857,"language":6731},[6729],"Обработчик пишет в буферизованный канал (неблокирующий\nдля большинства случаев).\nОтдельная горутина-воркер читает из канала и пишет в файл.\nЕсли канал полон (буфер переполнен) — можно дропать\nлоги или блокироваться в зависимости от требований.\n",[37,6859,6857],{"__ignoreMap":35},[19,6861],{},[15,6863,6864],{},[1075,6865,6866],{},"Ситуация 8",[15,6868,6869],{},"Нужно реализовать поиск по нескольким источникам (ElasticSearch, PostgreSQL full-text, внешний API). Возвращать объединённые результаты всех источников.",[15,6871,6872,6874],{},[1075,6873,6724],{}," Fan-out + Fan-in.",[30,6876,6879],{"className":6877,"code":6878,"language":6731},[6729],"Fan-out: три горутины параллельно делают поиск.\nFan-in: merge результатов из трёх каналов в один.\nДедупликация результатов уже в основной горутине.\nerrgroup для обработки ошибок — если один источник\nупал, всё равно возвращаем результаты от остальных\n(игнорируем ошибку или логируем).\n",[37,6880,6878],{"__ignoreMap":35},[19,6882],{},[6884,6885,6886],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":35,"searchDepth":50,"depth":50,"links":6888},[6889,6892,6893,6896,6897,6900,6901,6902,6903,6904],{"id":24,"depth":50,"text":25,"children":6890},[6891],{"id":630,"depth":87,"text":631},{"id":1070,"depth":50,"text":1071},{"id":1811,"depth":50,"text":1812,"children":6894},[6895],{"id":2489,"depth":87,"text":2490},{"id":2869,"depth":50,"text":2870},{"id":3220,"depth":50,"text":3221,"children":6898},[6899],{"id":3534,"depth":87,"text":3535},{"id":3683,"depth":50,"text":3684},{"id":4168,"depth":50,"text":4169},{"id":4411,"depth":50,"text":4412},{"id":4726,"depth":50,"text":4727},{"id":4812,"depth":50,"text":4813},"intermediate","md",{},"concurrency","lock-free","\u002F03-concurrency\u002F05-patterns","scheduler",{"title":5,"description":17},"concurrency-patterns","03-concurrency\u002F05-patterns\u002Findex",[6916,6917,6918,6919,6920,4796,6921],"pipeline","fan-in","fan-out","worker pool","semaphore","собеседование","jX8r6CowmYhTP0UwrXFm5z6yqQHADpuGcKez3b9BxOY",1776289835680]