[{"data":1,"prerenderedAt":1867},["ShallowReactive",2],{"content:\u002F03-concurrency\u002F04-scheduler":3},{"title":4,"description":5,"path":6,"body":7},"Планировщик Go","К этому моменту мы умеем запускать горутины, синхронизировать их через каналы и sync-примитивы, управлять отменой через контекст. Осталось понять как всё это работает внутри: кто решает какая горутина запустится следующей, как сотни тысяч горутин умещаются на нескольких OS-потоках и почему горутины не блокируют друг друга при системных вызовах. Это и есть планировщик Go.","\u002F03-concurrency\u002F04-scheduler",{"type":8,"value":9,"toc":1851},"minimark",[10,14,17,20,25,28,36,38,42,45,51,57,68,71,81,84,86,89,94,263,269,287,310,312,316,327,333,340,343,356,358,362,369,375,378,380,384,387,390,396,410,413,418,425,431,434,436,440,447,497,508,550,557,559,563,570,576,583,585,589,592,691,701,746,748,752,755,761,811,828,893,906,908,912,923,931,939,947,955,966,974,982,993,995,999,1002,1004,1009,1015,1021,1027,1084,1099,1104,1110,1112,1117,1122,1127,1132,1263,1267,1273,1275,1280,1285,1290,1295,1396,1400,1406,1408,1412,1457,1573,1847],[11,12,4],"h1",{"id":13},"планировщик-go",[15,16,5],"p",{},[18,19],"hr",{},[21,22,24],"h2",{"id":23},"проблема-которую-решает-планировщик","Проблема, которую решает планировщик",[15,26,27],{},"OS-потоки дороги: каждый занимает 1–8 МБ стека, создание требует syscall, переключение контекста — работа ядра. Если запустить 100 000 потоков — система встанет.",[15,29,30,31,35],{},"Go решает это через ",[32,33,34],"strong",{},"мультиплексирование",": тысячи горутин исполняются на небольшом фиксированном пуле OS-потоков. Планировщик Go работает в userspace и сам решает, какую горутину на каком потоке запустить — без участия ядра ОС.",[18,37],{},[21,39,41],{"id":40},"модель-gmp","Модель G\u002FM\u002FP",[15,43,44],{},"Планировщик Go построен на трёх сущностях:",[15,46,47,50],{},[32,48,49],{},"G (Goroutine)"," — горутина. Содержит стек, указатель на текущую инструкцию и служебные поля. Это единица работы с точки зрения планировщика.",[15,52,53,56],{},[32,54,55],{},"M (Machine)"," — OS-поток. Реальный поток операционной системы, который выполняет код. M нужен P чтобы исполнять горутины.",[15,58,59,62,63,67],{},[32,60,61],{},"P (Processor)"," — логический процессор. Содержит локальную очередь горутин и необходимые ресурсы для их исполнения. Количество P равно ",[64,65,66],"code",{},"GOMAXPROCS",".",[15,69,70],{},"Связь между ними:",[72,73,78],"pre",{"className":74,"code":76,"language":77},[75],"language-text","G  G  G  G       G  G  G          G  G\n|  |  |  |       |  |  |          |  |\n└──┴──┴──┘       └──┴──┘          └──┘\nLocal Queue P0   Local Queue P1   Local Queue P2\n     │                │                │\n     M0               M1               M2\n     │                │                │\n  OS Thread        OS Thread        OS Thread\n","text",[64,79,76],{"__ignoreMap":80},"",[15,82,83],{},"M исполняет горутины из очереди своего P. Без P — M не может исполнять горутины. Это ключевое ограничение, объясняющее поведение при syscall.",[18,85],{},[21,87,66],{"id":88},"gomaxprocs",[15,90,91,93],{},[64,92,66],{}," определяет количество P — то есть максимальное число горутин, исполняющихся параллельно:",[72,95,99],{"className":96,"code":97,"language":98,"meta":80,"style":80},"language-go shiki shiki-themes github-dark","package main\n\nimport (\n    \"fmt\"\n    \"runtime\"\n)\n\nfunc main() {\n    \u002F\u002F По умолчанию равно числу логических CPU\n    fmt.Println(runtime.GOMAXPROCS(0)) \u002F\u002F 0 = только прочитать, не менять\n\n    \u002F\u002F Изменить\n    runtime.GOMAXPROCS(4)\n    fmt.Println(runtime.GOMAXPROCS(0)) \u002F\u002F теперь 4\n}\n","go",[64,100,101,114,121,131,144,154,160,165,177,184,211,216,222,237,257],{"__ignoreMap":80},[102,103,106,110],"span",{"class":104,"line":105},"line",1,[102,107,109],{"class":108},"snl16","package",[102,111,113],{"class":112},"svObZ"," main\n",[102,115,117],{"class":104,"line":116},2,[102,118,120],{"emptyLinePlaceholder":119},true,"\n",[102,122,124,127],{"class":104,"line":123},3,[102,125,126],{"class":108},"import",[102,128,130],{"class":129},"s95oV"," (\n",[102,132,134,138,141],{"class":104,"line":133},4,[102,135,137],{"class":136},"sU2Wk","    \"",[102,139,140],{"class":112},"fmt",[102,142,143],{"class":136},"\"\n",[102,145,147,149,152],{"class":104,"line":146},5,[102,148,137],{"class":136},[102,150,151],{"class":112},"runtime",[102,153,143],{"class":136},[102,155,157],{"class":104,"line":156},6,[102,158,159],{"class":129},")\n",[102,161,163],{"class":104,"line":162},7,[102,164,120],{"emptyLinePlaceholder":119},[102,166,168,171,174],{"class":104,"line":167},8,[102,169,170],{"class":108},"func",[102,172,173],{"class":112}," main",[102,175,176],{"class":129},"() {\n",[102,178,180],{"class":104,"line":179},9,[102,181,183],{"class":182},"sAwPA","    \u002F\u002F По умолчанию равно числу логических CPU\n",[102,185,187,190,193,196,198,201,205,208],{"class":104,"line":186},10,[102,188,189],{"class":129},"    fmt.",[102,191,192],{"class":112},"Println",[102,194,195],{"class":129},"(runtime.",[102,197,66],{"class":112},[102,199,200],{"class":129},"(",[102,202,204],{"class":203},"sDLfK","0",[102,206,207],{"class":129},")) ",[102,209,210],{"class":182},"\u002F\u002F 0 = только прочитать, не менять\n",[102,212,214],{"class":104,"line":213},11,[102,215,120],{"emptyLinePlaceholder":119},[102,217,219],{"class":104,"line":218},12,[102,220,221],{"class":182},"    \u002F\u002F Изменить\n",[102,223,225,228,230,232,235],{"class":104,"line":224},13,[102,226,227],{"class":129},"    runtime.",[102,229,66],{"class":112},[102,231,200],{"class":129},[102,233,234],{"class":203},"4",[102,236,159],{"class":129},[102,238,240,242,244,246,248,250,252,254],{"class":104,"line":239},14,[102,241,189],{"class":129},[102,243,192],{"class":112},[102,245,195],{"class":129},[102,247,66],{"class":112},[102,249,200],{"class":129},[102,251,204],{"class":203},[102,253,207],{"class":129},[102,255,256],{"class":182},"\u002F\u002F теперь 4\n",[102,258,260],{"class":104,"line":259},15,[102,261,262],{"class":129},"}\n",[15,264,265,266,268],{},"По умолчанию ",[64,267,66],{}," равен числу логических ядер процессора. Это разумный дефолт для CPU-bound задач. Для IO-bound задач (большинство backend-сервисов) значение не имеет принципиального значения — горутины всё равно большую часть времени ждут IO, освобождая P.",[15,270,271,272,274,275,278,279,282,283,286],{},"В контейнерах до Go 1.25 была проблема: Go читал ",[64,273,66],{}," из ",[64,276,277],{},"\u002Fproc\u002Fcpuinfo"," хоста, а не из cgroups контейнера. Контейнер с лимитом в 2 CPU на хосте с 32 ядрами получал ",[64,280,281],{},"GOMAXPROCS=32"," — лишние потоки конкурировали за 2 реальных ядра. Решение — библиотека ",[64,284,285],{},"automaxprocs"," от Uber, которая читает лимиты из cgroups. Начиная с Go 1.25 это поведение исправлено в стандартной библиотеке.",[72,288,290],{"className":96,"code":289,"language":98,"meta":80,"style":80},"import _ \"go.uber.org\u002Fautomaxprocs\" \u002F\u002F автоматически для Go \u003C 1.25\n",[64,291,292],{"__ignoreMap":80},[102,293,294,296,299,302,305,307],{"class":104,"line":105},[102,295,126],{"class":108},[102,297,298],{"class":129}," _ ",[102,300,301],{"class":136},"\"",[102,303,304],{"class":112},"go.uber.org\u002Fautomaxprocs",[102,306,301],{"class":136},[102,308,309],{"class":182}," \u002F\u002F автоматически для Go \u003C 1.25\n",[18,311],{},[21,313,315],{"id":314},"глобальная-очередь-и-локальные-очереди","Глобальная очередь и локальные очереди",[15,317,318,319,322,323,326],{},"У каждого P есть ",[32,320,321],{},"локальная очередь"," горутин (до 256 элементов). Кроме этого существует одна ",[32,324,325],{},"глобальная очередь"," для всего рантайма:",[72,328,331],{"className":329,"code":330,"language":77},[75],"Global Queue: [G] [G] [G] [G] ...\n\nP0 Local: [G] [G] [G]    P1 Local: [G] [G]    P2 Local: []\n",[64,332,330],{"__ignoreMap":80},[15,334,335,336,339],{},"Когда горутина создаётся через ",[64,337,338],{},"go func()",", планировщик сначала пытается положить её в локальную очередь текущего P. Если локальная очередь переполнена — половина горутин переносится в глобальную очередь.",[15,341,342],{},"M берёт горутины для исполнения в следующем порядке:",[344,345,346,350,353],"ol",{},[347,348,349],"li",{},"Из локальной очереди своего P",[347,351,352],{},"Из глобальной очереди (раз в ~61 такт, чтобы глобальные горутины не голодали)",[347,354,355],{},"Через work stealing у других P",[18,357],{},[21,359,361],{"id":360},"work-stealing","Work Stealing",[15,363,364,365,368],{},"Если локальная очередь P пуста — M не простаивает, а ",[32,366,367],{},"крадёт"," горутины у других P:",[72,370,373],{"className":371,"code":372,"language":77},[75],"P0 Local: []  ← пусто        P1 Local: [G1] [G2] [G3] [G4]\n\nP0 крадёт половину у P1 →\n\nP0 Local: [G3] [G4]           P1 Local: [G1] [G2]\n",[64,374,372],{"__ignoreMap":80},[15,376,377],{},"M с пустым P крадёт ровно половину горутин из случайно выбранного P. Это обеспечивает равномерное распределение нагрузки без централизованной координации — каждый P самостоятельно балансирует нагрузку.",[18,379],{},[21,381,383],{"id":382},"что-происходит-при-системном-вызове","Что происходит при системном вызове",[15,385,386],{},"Здесь планировщик делает нечто умное. Когда горутина выполняет блокирующий syscall (например, чтение файла) — она блокирует весь M, потому что syscall выполняется на уровне OS-потока.",[15,388,389],{},"Если бы P остался привязан к этому M — никакие другие горутины не могли бы исполняться. Планировщик решает это так:",[72,391,394],{"className":392,"code":393,"language":77},[75],"До syscall:                     Во время syscall:\n\nP ── M0 ── G (syscall)          M0 ── G (syscall) ← P отвязан\n│                               \n└── очередь горутин             P ── M1 (новый или из пула)\n                                │\n                                └── очередь горутин (продолжают работу)\n",[64,395,393],{"__ignoreMap":80},[344,397,398,401,404,407],{},[347,399,400],{},"Перед syscall P отвязывается от M0",[347,402,403],{},"M1 (свободный поток из пула или новый) подхватывает P",[347,405,406],{},"Остальные горутины продолжают исполняться на M1",[347,408,409],{},"Когда syscall завершается — M0 пытается получить P обратно. Если P свободен — забирает. Если нет — G кладётся в глобальную очередь, M0 уходит в пул",[15,411,412],{},"Именно поэтому Go может эффективно обрабатывать тысячи конкурентных IO-операций: каждая горутина блокируется только на своём syscall, не мешая остальным.",[414,415,417],"h3",{"id":416},"сетевой-io-особый-случай","Сетевой IO — особый случай",[15,419,420,421,424],{},"Сетевой IO в Go обрабатывается иначе через ",[32,422,423],{},"netpoller"," — асинхронный механизм на основе epoll (Linux), kqueue (macOS) или IOCP (Windows):",[72,426,429],{"className":427,"code":428,"language":77},[75],"Горутина делает net.Read()\n       │\n       ├── данные готовы → читаем сразу, горутина не блокируется\n       │\n       └── данных нет → горутина паркуется (не блокирует M!)\n                        netpoller ждёт данных через epoll\n                        когда данные пришли → горутина возвращается в очередь\n",[64,430,428],{"__ignoreMap":80},[15,432,433],{},"Сетевая горутина паркуется без блокировки M — поэтому тысячи горутин с открытыми TCP-соединениями не создают тысячи OS-потоков. M освобождается и исполняет другие горутины.",[18,435],{},[21,437,439],{"id":438},"preemption-вытеснение-горутин","Preemption — вытеснение горутин",[15,441,442,443,446],{},"Ранние версии Go (до 1.14) использовали ",[32,444,445],{},"кооперативное вытеснение",": горутина могла быть вытеснена только в точках, где она явно передавала управление — при вызове функций, операциях с каналами, syscall. Горутина в плотном цикле без вызовов функций могла монополизировать M:",[72,448,450],{"className":96,"code":449,"language":98,"meta":80,"style":80},"\u002F\u002F До Go 1.14: этот цикл держал M и не давал другим горутинам работать\ngo func() {\n    for {\n        \u002F\u002F плотный цикл без function calls — планировщик не может вытеснить\n        i++\n    }\n}()\n",[64,451,452,457,466,474,479,487,492],{"__ignoreMap":80},[102,453,454],{"class":104,"line":105},[102,455,456],{"class":182},"\u002F\u002F До Go 1.14: этот цикл держал M и не давал другим горутинам работать\n",[102,458,459,461,464],{"class":104,"line":116},[102,460,98],{"class":108},[102,462,463],{"class":108}," func",[102,465,176],{"class":129},[102,467,468,471],{"class":104,"line":123},[102,469,470],{"class":108},"    for",[102,472,473],{"class":129}," {\n",[102,475,476],{"class":104,"line":133},[102,477,478],{"class":182},"        \u002F\u002F плотный цикл без function calls — планировщик не может вытеснить\n",[102,480,481,484],{"class":104,"line":146},[102,482,483],{"class":129},"        i",[102,485,486],{"class":108},"++\n",[102,488,489],{"class":104,"line":156},[102,490,491],{"class":129},"    }\n",[102,493,494],{"class":104,"line":162},[102,495,496],{"class":129},"}()\n",[15,498,499,500,503,504,507],{},"С Go 1.14 введено ",[32,501,502],{},"асинхронное вытеснение"," (asynchronous preemption): планировщик посылает горутине сигнал ",[64,505,506],{},"SIGURG"," (на Unix), который прерывает исполнение в любой точке кода — даже в середине плотного цикла. Горутина сохраняет состояние и помещается обратно в очередь:",[72,509,511],{"className":96,"code":510,"language":98,"meta":80,"style":80},"\u002F\u002F Go 1.14+: этот цикл корректно вытесняется планировщиком\ngo func() {\n    for {\n        i++ \u002F\u002F SIGURG может прийти в любой момент\n    }\n}()\n",[64,512,513,518,526,532,542,546],{"__ignoreMap":80},[102,514,515],{"class":104,"line":105},[102,516,517],{"class":182},"\u002F\u002F Go 1.14+: этот цикл корректно вытесняется планировщиком\n",[102,519,520,522,524],{"class":104,"line":116},[102,521,98],{"class":108},[102,523,463],{"class":108},[102,525,176],{"class":129},[102,527,528,530],{"class":104,"line":123},[102,529,470],{"class":108},[102,531,473],{"class":129},[102,533,534,536,539],{"class":104,"line":133},[102,535,483],{"class":129},[102,537,538],{"class":108},"++",[102,540,541],{"class":182}," \u002F\u002F SIGURG может прийти в любой момент\n",[102,543,544],{"class":104,"line":146},[102,545,491],{"class":129},[102,547,548],{"class":104,"line":156},[102,549,496],{"class":129},[15,551,552,553,556],{},"Вытеснение происходит примерно каждые ",[32,554,555],{},"10 мс"," — это квант времени Go-планировщика.",[18,558],{},[21,560,562],{"id":561},"spinning-потоки","spinning потоки",[15,564,565,566,569],{},"Когда очереди горутин пусты, M не засыпает немедленно. Несколько M переходят в ",[32,567,568],{},"spinning"," режим — активно опрашивают очереди в ожидании новых горутин:",[72,571,574],{"className":572,"code":573,"language":77},[75],"M0: исполняет G\nM1: spinning ← активно проверяет очереди\nM2: sleeping  ← припаркован, ждёт сигнала\n",[64,575,573],{"__ignoreMap":80},[15,577,578,579,582],{},"Spinning позволяет мгновенно подхватить новую горутину без latency на пробуждение потока (которое занимает микросекунды). Количество spinning потоков ограничено — не более ",[64,580,581],{},"GOMAXPROCS\u002F2",", чтобы не жечь CPU впустую.",[18,584],{},[21,586,588],{"id":587},"runtimegosched-и-ручное-управление","runtime.Gosched и ручное управление",[15,590,591],{},"В редких случаях горутина может явно уступить управление:",[72,593,595],{"className":96,"code":594,"language":98,"meta":80,"style":80},"func heavyComputation() {\n    for i := 0; i \u003C 1_000_000_000; i++ {\n        doWork(i)\n        if i%10_000 == 0 {\n            runtime.Gosched() \u002F\u002F явно уступаем — даём другим горутинам поработать\n        }\n    }\n}\n",[64,596,597,606,635,643,664,678,683,687],{"__ignoreMap":80},[102,598,599,601,604],{"class":104,"line":105},[102,600,170],{"class":108},[102,602,603],{"class":112}," heavyComputation",[102,605,176],{"class":129},[102,607,608,610,613,616,619,622,625,628,631,633],{"class":104,"line":116},[102,609,470],{"class":108},[102,611,612],{"class":129}," i ",[102,614,615],{"class":108},":=",[102,617,618],{"class":203}," 0",[102,620,621],{"class":129},"; i ",[102,623,624],{"class":108},"\u003C",[102,626,627],{"class":203}," 1_000_000_000",[102,629,630],{"class":129},"; i",[102,632,538],{"class":108},[102,634,473],{"class":129},[102,636,637,640],{"class":104,"line":123},[102,638,639],{"class":112},"        doWork",[102,641,642],{"class":129},"(i)\n",[102,644,645,648,651,654,657,660,662],{"class":104,"line":133},[102,646,647],{"class":108},"        if",[102,649,650],{"class":129}," i",[102,652,653],{"class":108},"%",[102,655,656],{"class":203},"10_000",[102,658,659],{"class":108}," ==",[102,661,618],{"class":203},[102,663,473],{"class":129},[102,665,666,669,672,675],{"class":104,"line":146},[102,667,668],{"class":129},"            runtime.",[102,670,671],{"class":112},"Gosched",[102,673,674],{"class":129},"() ",[102,676,677],{"class":182},"\u002F\u002F явно уступаем — даём другим горутинам поработать\n",[102,679,680],{"class":104,"line":156},[102,681,682],{"class":129},"        }\n",[102,684,685],{"class":104,"line":162},[102,686,491],{"class":129},[102,688,689],{"class":104,"line":167},[102,690,262],{"class":129},[15,692,693,696,697,700],{},[64,694,695],{},"runtime.Gosched()"," — аналог ",[64,698,699],{},"yield"," в других языках. Горутина помещается в конец очереди, планировщик выбирает следующую. С Go 1.14 это редко нужно — асинхронное вытеснение справляется само.",[72,702,704],{"className":96,"code":703,"language":98,"meta":80,"style":80},"\u002F\u002F Другие полезные функции\nruntime.NumGoroutine()  \u002F\u002F текущее количество горутин\nruntime.NumCPU()        \u002F\u002F число логических CPU\nruntime.GOOS            \u002F\u002F операционная система (константа)\n",[64,705,706,711,725,738],{"__ignoreMap":80},[102,707,708],{"class":104,"line":105},[102,709,710],{"class":182},"\u002F\u002F Другие полезные функции\n",[102,712,713,716,719,722],{"class":104,"line":116},[102,714,715],{"class":129},"runtime.",[102,717,718],{"class":112},"NumGoroutine",[102,720,721],{"class":129},"()  ",[102,723,724],{"class":182},"\u002F\u002F текущее количество горутин\n",[102,726,727,729,732,735],{"class":104,"line":123},[102,728,715],{"class":129},[102,730,731],{"class":112},"NumCPU",[102,733,734],{"class":129},"()        ",[102,736,737],{"class":182},"\u002F\u002F число логических CPU\n",[102,739,740,743],{"class":104,"line":133},[102,741,742],{"class":129},"runtime.GOOS            ",[102,744,745],{"class":182},"\u002F\u002F операционная система (константа)\n",[18,747],{},[21,749,751],{"id":750},"как-планировщик-влияет-на-код","Как планировщик влияет на код",[15,753,754],{},"Понимание планировщика объясняет несколько неочевидных вещей:",[15,756,757,760],{},[32,758,759],{},"Порядок исполнения горутин не гарантирован."," Даже если горутина создана раньше — она может исполниться позже. Это зависит от состояния очередей и work stealing:",[72,762,765],{"className":96,"code":763,"language":98,"meta":764,"style":80},"for i := 0; i \u003C 5; i++ {\n    go fmt.Println(i) \u002F\u002F порядок вывода непредсказуем\n}\n","static",[64,766,767,791,807],{"__ignoreMap":80},[102,768,769,772,774,776,778,780,782,785,787,789],{"class":104,"line":105},[102,770,771],{"class":108},"for",[102,773,612],{"class":129},[102,775,615],{"class":108},[102,777,618],{"class":203},[102,779,621],{"class":129},[102,781,624],{"class":108},[102,783,784],{"class":203}," 5",[102,786,630],{"class":129},[102,788,538],{"class":108},[102,790,473],{"class":129},[102,792,793,796,799,801,804],{"class":104,"line":116},[102,794,795],{"class":108},"    go",[102,797,798],{"class":129}," fmt.",[102,800,192],{"class":112},[102,802,803],{"class":129},"(i) ",[102,805,806],{"class":182},"\u002F\u002F порядок вывода непредсказуем\n",[102,808,809],{"class":104,"line":123},[102,810,262],{"class":129},[15,812,813,819,820,823,824,827],{},[32,814,815,818],{},[64,816,817],{},"runtime.LockOSThread()"," — привязка к потоку."," Некоторые C-библиотеки (OpenGL, GUI) требуют вызовов из одного и того же OS-потока. ",[64,821,822],{},"LockOSThread"," привязывает текущую горутину к текущему M навсегда (пока не вызовет ",[64,825,826],{},"UnlockOSThread","):",[72,829,831],{"className":96,"code":830,"language":98,"meta":80,"style":80},"func glWorker() {\n    runtime.LockOSThread()\n    defer runtime.UnlockOSThread()\n\n    \u002F\u002F все вызовы OpenGL из этой горутины — на одном M\n    gl.Init()\n    renderLoop()\n}\n",[64,832,833,842,851,863,867,872,882,889],{"__ignoreMap":80},[102,834,835,837,840],{"class":104,"line":105},[102,836,170],{"class":108},[102,838,839],{"class":112}," glWorker",[102,841,176],{"class":129},[102,843,844,846,848],{"class":104,"line":116},[102,845,227],{"class":129},[102,847,822],{"class":112},[102,849,850],{"class":129},"()\n",[102,852,853,856,859,861],{"class":104,"line":123},[102,854,855],{"class":108},"    defer",[102,857,858],{"class":129}," runtime.",[102,860,826],{"class":112},[102,862,850],{"class":129},[102,864,865],{"class":104,"line":133},[102,866,120],{"emptyLinePlaceholder":119},[102,868,869],{"class":104,"line":146},[102,870,871],{"class":182},"    \u002F\u002F все вызовы OpenGL из этой горутины — на одном M\n",[102,873,874,877,880],{"class":104,"line":156},[102,875,876],{"class":129},"    gl.",[102,878,879],{"class":112},"Init",[102,881,850],{"class":129},[102,883,884,887],{"class":104,"line":162},[102,885,886],{"class":112},"    renderLoop",[102,888,850],{"class":129},[102,890,891],{"class":104,"line":167},[102,892,262],{"class":129},[15,894,895,898,899,901,902,905],{},[32,896,897],{},"Горутины на syscall создают дополнительные M."," Если все горутины одновременно делают блокирующие syscall — рантайм создаёт новые M. Их количество не ограничено ",[64,900,66],{}," и может стать очень большим. В экстремальных случаях это приводит к исчерпанию ресурсов. Ограничение через ",[64,903,904],{},"runtime\u002Fdebug.SetMaxThreads"," (по умолчанию 10 000).",[18,907],{},[21,909,911],{"id":910},"вопросы-на-собеседовании","Вопросы на собеседовании",[15,913,914,917,920,921,67],{},[32,915,916],{},"Q: Что такое G, M и P в планировщике Go?",[918,919],"br",{},"\nA: G — горутина, единица работы со своим стеком и контекстом исполнения. M — OS-поток, реальный поток операционной системы. P — логический процессор, содержит локальную очередь горутин и необходимые ресурсы. M исполняет горутины только при наличии P. Количество P определяется ",[64,922,66],{},[15,924,925,928,930],{},[32,926,927],{},"Q: Что такое work stealing?",[918,929],{},"\nA: Механизм балансировки нагрузки: если локальная очередь P пуста, M крадёт половину горутин из случайно выбранного другого P. Это обеспечивает равномерное распределение без централизованной координации и позволяет избежать простоя потоков.",[15,932,933,936,938],{},[32,934,935],{},"Q: Что происходит когда горутина делает блокирующий syscall?",[918,937],{},"\nA: P отвязывается от текущего M. Свободный M (или новый) подхватывает P и продолжает исполнять другие горутины. Когда syscall завершается, M пытается вернуть P. Если P занят — горутина кладётся в глобальную очередь, M уходит в пул.",[15,940,941,944,946],{},[32,942,943],{},"Q: Как Go обрабатывает тысячи сетевых соединений не создавая тысячи потоков?",[918,945],{},"\nA: Через netpoller на основе epoll\u002Fkqueue\u002FIOCP. Горутина, ожидающая сетевого IO, паркуется без блокировки M — поток освобождается и исполняет другие горутины. Когда данные готовы, netpoller возвращает горутину в очередь планировщика.",[15,948,949,952,954],{},[32,950,951],{},"Q: Что такое кооперативное и асинхронное вытеснение? Когда изменилось?",[918,953],{},"\nA: До Go 1.14 — кооперативное: горутина вытесняется только в точках явной передачи управления (вызов функции, канал, syscall). Плотный цикл без вызовов мог монополизировать поток. С Go 1.14 — асинхронное: планировщик посылает SIGURG, прерывающий горутину в любой точке. Квант времени — ~10 мс.",[15,956,957,960,962,963,965],{},[32,958,959],{},"Q: Что такое GOMAXPROCS и как правильно настраивать в контейнере?",[918,961],{},"\nA: Количество P — максимум параллельно исполняемых горутин. По умолчанию равно числу логических CPU. В контейнерах до Go 1.25 читался из хоста, а не из cgroups — решение: ",[64,964,285],{}," от Uber. В Go 1.25 исправлено в стандартной библиотеке.",[15,967,968,971,973],{},[32,969,970],{},"Q: Зачем нужны spinning потоки?",[918,972],{},"\nA: Когда очереди пусты, часть M активно опрашивают очереди вместо немедленного засыпания. Это позволяет мгновенно подхватить новую горутину без latency на пробуждение потока. Количество spinning M ограничено GOMAXPROCS\u002F2.",[15,975,976,979,981],{},[32,977,978],{},"Q: Когда и зачем использовать runtime.LockOSThread?",[918,980],{},"\nA: Когда C-библиотека требует вызовов из одного и того же OS-потока (OpenGL, некоторые GUI-фреймворки, CGo с thread-local state). LockOSThread привязывает горутину к текущему M — планировщик не перенесёт её на другой поток.",[15,983,984,987,989,990,992],{},[32,985,986],{},"Q: Почему количество M не ограничено GOMAXPROCS?",[918,988],{},"\nA: GOMAXPROCS ограничивает только P — активно исполняющие горутины. M создаются под каждый блокирующий syscall чтобы P мог продолжать работу с другим M. В теории M может быть намного больше GOMAXPROCS если много горутин одновременно в syscall. Лимит — ",[64,991,904],{},", по умолчанию 10 000.",[18,994],{},[11,996,998],{"id":997},"задачи-планировщик","Задачи: Планировщик",[15,1000,1001],{},"Задачи по планировщику — концептуальные. Здесь важно объяснение, а не код.",[18,1003],{},[15,1005,1006],{},[32,1007,1008],{},"Задача 1: Сколько реально параллельно",[15,1010,1011,1014],{},[32,1012,1013],{},"Уровень:"," Лёгкая",[15,1016,1017,1020],{},[32,1018,1019],{},"Что проверяет:"," понимание GOMAXPROCS и разницы между конкурентностью и параллелизмом",[15,1022,1023,1026],{},[32,1024,1025],{},"Условие:"," Ответь на вопросы и объясни:",[72,1028,1030],{"className":96,"code":1029,"language":98,"meta":80,"style":80},"runtime.GOMAXPROCS(2)\n\nfor i := 0; i \u003C 10; i++ {\n    go heavyComputation()\n}\n",[64,1031,1032,1045,1049,1072,1080],{"__ignoreMap":80},[102,1033,1034,1036,1038,1040,1043],{"class":104,"line":105},[102,1035,715],{"class":129},[102,1037,66],{"class":112},[102,1039,200],{"class":129},[102,1041,1042],{"class":203},"2",[102,1044,159],{"class":129},[102,1046,1047],{"class":104,"line":116},[102,1048,120],{"emptyLinePlaceholder":119},[102,1050,1051,1053,1055,1057,1059,1061,1063,1066,1068,1070],{"class":104,"line":123},[102,1052,771],{"class":108},[102,1054,612],{"class":129},[102,1056,615],{"class":108},[102,1058,618],{"class":203},[102,1060,621],{"class":129},[102,1062,624],{"class":108},[102,1064,1065],{"class":203}," 10",[102,1067,630],{"class":129},[102,1069,538],{"class":108},[102,1071,473],{"class":129},[102,1073,1074,1076,1078],{"class":104,"line":133},[102,1075,795],{"class":108},[102,1077,603],{"class":112},[102,1079,850],{"class":129},[102,1081,1082],{"class":104,"line":146},[102,1083,262],{"class":129},[344,1085,1086,1089,1092],{},[347,1087,1088],{},"Сколько горутин запущено?",[347,1090,1091],{},"Сколько из них выполняются параллельно в один момент времени?",[347,1093,1094,1095,1098],{},"Что изменится если ",[64,1096,1097],{},"GOMAXPROCS(1)","?",[15,1100,1101],{},[32,1102,1103],{},"Решение:",[72,1105,1108],{"className":1106,"code":1107,"language":77},[75],"1. Запущено 10 горутин.\n\n2. Параллельно выполняются максимум 2 — по числу P (GOMAXPROCS=2).\n   Остальные 8 ждут в локальных очередях P.\n\n3. При GOMAXPROCS(1) — только 1 P, горутины выполняются\n   конкурентно но не параллельно. Планировщик переключает\n   их на одном OS-потоке. Для CPU-bound задач это в 2 раза\n   медленнее чем GOMAXPROCS(2).\n\nВажно: конкурентность (много горутин) ≠ параллелизм\n(одновременное выполнение). Параллелизм ограничен GOMAXPROCS.\n",[64,1109,1107],{"__ignoreMap":80},[18,1111],{},[15,1113,1114],{},[32,1115,1116],{},"Задача 2: Почему не зависает",[15,1118,1119,1121],{},[32,1120,1013],{}," Средняя",[15,1123,1124,1126],{},[32,1125,1019],{}," понимание планировщика при syscall, netpoller",[15,1128,1129,1131],{},[32,1130,1025],{}," Объясни почему этот код не блокирует сервер несмотря на то что каждый запрос делает сетевой вызов:",[72,1133,1135],{"className":96,"code":1134,"language":98,"meta":80,"style":80},"http.HandleFunc(\"\u002F\", func(w http.ResponseWriter, r *http.Request) {\n    resp, _ := http.Get(\"https:\u002F\u002Fapi.example.com\u002Fdata\") \u002F\u002F сетевой вызов\n    defer resp.Body.Close()\n    io.Copy(w, resp.Body)\n})\n\nhttp.ListenAndServe(\":8080\", nil)\n",[64,1136,1137,1188,1212,1224,1235,1240,1244],{"__ignoreMap":80},[102,1138,1139,1142,1145,1147,1150,1153,1155,1157,1161,1164,1166,1169,1171,1174,1177,1180,1182,1185],{"class":104,"line":105},[102,1140,1141],{"class":129},"http.",[102,1143,1144],{"class":112},"HandleFunc",[102,1146,200],{"class":129},[102,1148,1149],{"class":136},"\"\u002F\"",[102,1151,1152],{"class":129},", ",[102,1154,170],{"class":108},[102,1156,200],{"class":129},[102,1158,1160],{"class":1159},"s9osk","w",[102,1162,1163],{"class":112}," http",[102,1165,67],{"class":129},[102,1167,1168],{"class":112},"ResponseWriter",[102,1170,1152],{"class":129},[102,1172,1173],{"class":1159},"r",[102,1175,1176],{"class":108}," *",[102,1178,1179],{"class":112},"http",[102,1181,67],{"class":129},[102,1183,1184],{"class":112},"Request",[102,1186,1187],{"class":129},") {\n",[102,1189,1190,1193,1195,1198,1201,1203,1206,1209],{"class":104,"line":116},[102,1191,1192],{"class":129},"    resp, _ ",[102,1194,615],{"class":108},[102,1196,1197],{"class":129}," http.",[102,1199,1200],{"class":112},"Get",[102,1202,200],{"class":129},[102,1204,1205],{"class":136},"\"https:\u002F\u002Fapi.example.com\u002Fdata\"",[102,1207,1208],{"class":129},") ",[102,1210,1211],{"class":182},"\u002F\u002F сетевой вызов\n",[102,1213,1214,1216,1219,1222],{"class":104,"line":123},[102,1215,855],{"class":108},[102,1217,1218],{"class":129}," resp.Body.",[102,1220,1221],{"class":112},"Close",[102,1223,850],{"class":129},[102,1225,1226,1229,1232],{"class":104,"line":133},[102,1227,1228],{"class":129},"    io.",[102,1230,1231],{"class":112},"Copy",[102,1233,1234],{"class":129},"(w, resp.Body)\n",[102,1236,1237],{"class":104,"line":146},[102,1238,1239],{"class":129},"})\n",[102,1241,1242],{"class":104,"line":156},[102,1243,120],{"emptyLinePlaceholder":119},[102,1245,1246,1248,1251,1253,1256,1258,1261],{"class":104,"line":162},[102,1247,1141],{"class":129},[102,1249,1250],{"class":112},"ListenAndServe",[102,1252,200],{"class":129},[102,1254,1255],{"class":136},"\":8080\"",[102,1257,1152],{"class":129},[102,1259,1260],{"class":203},"nil",[102,1262,159],{"class":129},[15,1264,1265],{},[32,1266,1103],{},[72,1268,1271],{"className":1269,"code":1270,"language":77},[75],"Каждый входящий запрос обрабатывается в отдельной горутине.\nПри вызове http.Get — сетевой IO обрабатывается через netpoller\n(epoll на Linux).\n\nКогда горутина ждёт ответа от api.example.com:\n1. Горутина паркуется — не блокирует свой M (OS-поток)\n2. netpoller регистрирует дескриптор в epoll\n3. M освобождается и берёт другую горутину из очереди\n4. Когда данные пришли — netpoller возвращает горутину в очередь\n\nРезультат: 10 000 одновременных запросов к внешнему API\n= 10 000 припаркованных горутин, но всего GOMAXPROCS активных потоков.\nСервер не блокируется.\n",[64,1272,1270],{"__ignoreMap":80},[18,1274],{},[15,1276,1277],{},[32,1278,1279],{},"Задача 3: Плотный цикл",[15,1281,1282,1284],{},[32,1283,1013],{}," Сложная",[15,1286,1287,1289],{},[32,1288,1019],{}," понимание preemption, разница до и после Go 1.14",[15,1291,1292,1294],{},[32,1293,1025],{}," Что произойдёт при запуске этого кода на Go 1.13 и на Go 1.14+? Объясни разницу.",[72,1296,1298],{"className":96,"code":1297,"language":98,"meta":80,"style":80},"runtime.GOMAXPROCS(1)\n\ngo func() {\n    for {\n        \u002F\u002F плотный цикл без вызовов функций\n        _ = 1 + 1\n    }\n}()\n\ntime.Sleep(time.Second)\n\u002F\u002F Go 1.13: этот print никогда не достигается\n\u002F\u002F Go 1.14+: выводит \"main продолжается\"\n_ = \"main продолжается\"\n",[64,1299,1300,1313,1317,1325,1331,1336,1353,1357,1361,1365,1376,1381,1386],{"__ignoreMap":80},[102,1301,1302,1304,1306,1308,1311],{"class":104,"line":105},[102,1303,715],{"class":129},[102,1305,66],{"class":112},[102,1307,200],{"class":129},[102,1309,1310],{"class":203},"1",[102,1312,159],{"class":129},[102,1314,1315],{"class":104,"line":116},[102,1316,120],{"emptyLinePlaceholder":119},[102,1318,1319,1321,1323],{"class":104,"line":123},[102,1320,98],{"class":108},[102,1322,463],{"class":108},[102,1324,176],{"class":129},[102,1326,1327,1329],{"class":104,"line":133},[102,1328,470],{"class":108},[102,1330,473],{"class":129},[102,1332,1333],{"class":104,"line":146},[102,1334,1335],{"class":182},"        \u002F\u002F плотный цикл без вызовов функций\n",[102,1337,1338,1341,1344,1347,1350],{"class":104,"line":156},[102,1339,1340],{"class":129},"        _ ",[102,1342,1343],{"class":108},"=",[102,1345,1346],{"class":203}," 1",[102,1348,1349],{"class":108}," +",[102,1351,1352],{"class":203}," 1\n",[102,1354,1355],{"class":104,"line":162},[102,1356,491],{"class":129},[102,1358,1359],{"class":104,"line":167},[102,1360,496],{"class":129},[102,1362,1363],{"class":104,"line":179},[102,1364,120],{"emptyLinePlaceholder":119},[102,1366,1367,1370,1373],{"class":104,"line":186},[102,1368,1369],{"class":129},"time.",[102,1371,1372],{"class":112},"Sleep",[102,1374,1375],{"class":129},"(time.Second)\n",[102,1377,1378],{"class":104,"line":213},[102,1379,1380],{"class":182},"\u002F\u002F Go 1.13: этот print никогда не достигается\n",[102,1382,1383],{"class":104,"line":218},[102,1384,1385],{"class":182},"\u002F\u002F Go 1.14+: выводит \"main продолжается\"\n",[102,1387,1388,1391,1393],{"class":104,"line":224},[102,1389,1390],{"class":129},"_ ",[102,1392,1343],{"class":108},[102,1394,1395],{"class":136}," \"main продолжается\"\n",[15,1397,1398],{},[32,1399,1103],{},[72,1401,1404],{"className":1402,"code":1403,"language":77},[75],"Go 1.13 (кооперативное вытеснение):\nГорутина в плотном цикле без вызовов функций НИКОГДА\nне отдаёт управление планировщику. При GOMAXPROCS(1) есть\nтолько один P — он занят бесконечным циклом.\ntime.Sleep в main никогда не сработает — deadlock или\nmain никогда не продолжится.\n\nGo 1.14+ (асинхронное вытеснение):\nПланировщик посылает SIGURG каждые ~10ms.\nГорутина прерывается в любой точке — даже внутри `_ = 1+1`.\nСохраняется состояние регистров, горутина кладётся в очередь.\nmain продолжается через секунду, выводит \"main продолжается\".\n\nВывод: до Go 1.14 плотные циклы без function calls\nмогли монополизировать поток. После 1.14 — нет.\nruntime.Gosched() раньше был обязателен в таких циклах.\n",[64,1405,1403],{"__ignoreMap":80},[18,1407],{},[21,1409,1411],{"id":1410},"практика","Практика",[1413,1414,1417,1422,1447],"quiz",{"answer":1042,"id":1415,"xp":1416},"concurrency-scheduler-q1","10",[15,1418,1419,1420,1098],{},"Что в первую очередь задаёт ",[64,1421,66],{},[1423,1424,1425],"template",{"v-slot:options":80},[1426,1427,1428,1431,1438,1441],"ul",{},[347,1429,1430],{},"Максимальное количество горутин в программе",[347,1432,1433,1434,1437],{},"Количество ",[64,1435,1436],{},"P",", которые могут одновременно выполнять Go-код",[347,1439,1440],{},"Размер стека каждой новой горутины",[347,1442,1443,1444],{},"Количество каналов, которые можно читать через ",[64,1445,1446],{},"select",[1423,1448,1449],{"v-slot:explanation":80},[15,1450,1451,1453,1454,1456],{},[64,1452,66],{}," задаёт число процессоров планировщика (",[64,1455,1436],{},"), то есть сколько Go-кода может выполняться параллельно. Горутин может быть намного больше: остальные будут ждать в очередях, спать на IO или быть припаркованными.",[1458,1459,1463,1466,1562],"predict",{"answer":1460,"id":1461,"xp":1462},"true","concurrency-scheduler-p1","15",[15,1464,1465],{},"Что выведет программа?",[1423,1467,1468],{"v-slot:code":80},[72,1469,1471],{"className":96,"code":1470,"language":98,"meta":80,"style":80},"package main\n\nimport (\n    \"fmt\"\n    \"runtime\"\n)\n\nfunc main() {\n    old := runtime.GOMAXPROCS(1)\n    fmt.Println(runtime.GOMAXPROCS(old) >= 1)\n}\n",[64,1472,1473,1479,1483,1489,1497,1505,1509,1513,1521,1538,1558],{"__ignoreMap":80},[102,1474,1475,1477],{"class":104,"line":105},[102,1476,109],{"class":108},[102,1478,113],{"class":112},[102,1480,1481],{"class":104,"line":116},[102,1482,120],{"emptyLinePlaceholder":119},[102,1484,1485,1487],{"class":104,"line":123},[102,1486,126],{"class":108},[102,1488,130],{"class":129},[102,1490,1491,1493,1495],{"class":104,"line":133},[102,1492,137],{"class":136},[102,1494,140],{"class":112},[102,1496,143],{"class":136},[102,1498,1499,1501,1503],{"class":104,"line":146},[102,1500,137],{"class":136},[102,1502,151],{"class":112},[102,1504,143],{"class":136},[102,1506,1507],{"class":104,"line":156},[102,1508,159],{"class":129},[102,1510,1511],{"class":104,"line":162},[102,1512,120],{"emptyLinePlaceholder":119},[102,1514,1515,1517,1519],{"class":104,"line":167},[102,1516,170],{"class":108},[102,1518,173],{"class":112},[102,1520,176],{"class":129},[102,1522,1523,1526,1528,1530,1532,1534,1536],{"class":104,"line":179},[102,1524,1525],{"class":129},"    old ",[102,1527,615],{"class":108},[102,1529,858],{"class":129},[102,1531,66],{"class":112},[102,1533,200],{"class":129},[102,1535,1310],{"class":203},[102,1537,159],{"class":129},[102,1539,1540,1542,1544,1546,1548,1551,1554,1556],{"class":104,"line":186},[102,1541,189],{"class":129},[102,1543,192],{"class":112},[102,1545,195],{"class":129},[102,1547,66],{"class":112},[102,1549,1550],{"class":129},"(old) ",[102,1552,1553],{"class":108},">=",[102,1555,1346],{"class":203},[102,1557,159],{"class":129},[102,1559,1560],{"class":104,"line":213},[102,1561,262],{"class":129},[1423,1563,1564],{"v-slot:hint":80},[15,1565,1566,1569,1570,1572],{},[64,1567,1568],{},"runtime.GOMAXPROCS(1)"," возвращает старое значение. Второй вызов восстанавливает его и возвращает текущее значение до восстановления — это ",[64,1571,1310],{},", поэтому сравнение истинно.",[1574,1575,1579,1586,1823],"code-task",{"expected":1576,"id":1577,"xp":1578},"3","concurrency-scheduler-ct1","20",[15,1580,1581,1582,1585],{},"Реализуй ",[64,1583,1584],{},"runConcurrently",": функция должна запустить все переданные задачи в отдельных горутинах и дождаться их завершения.",[1423,1587,1588],{"v-slot:template":80},[72,1589,1591],{"className":96,"code":1590,"language":98,"meta":80,"style":80},"package main\n\nimport (\n    \"fmt\"\n    \"sync\"\n)\n\nfunc runConcurrently(jobs []func()) {\n    var wg sync.WaitGroup\n    _ = wg\n    _ = jobs\n}\n\nfunc main() {\n    done := make(chan int, 3)\n\n    runConcurrently([]func(){\n        func() { done \u003C- 1 },\n        func() { done \u003C- 2 },\n        func() { done \u003C- 3 },\n    })\n\n    fmt.Println(len(done))\n}\n",[64,1592,1593,1599,1603,1609,1617,1626,1630,1634,1654,1669,1679,1688,1692,1696,1704,1728,1733,1747,1764,1778,1792,1798,1803,1818],{"__ignoreMap":80},[102,1594,1595,1597],{"class":104,"line":105},[102,1596,109],{"class":108},[102,1598,113],{"class":112},[102,1600,1601],{"class":104,"line":116},[102,1602,120],{"emptyLinePlaceholder":119},[102,1604,1605,1607],{"class":104,"line":123},[102,1606,126],{"class":108},[102,1608,130],{"class":129},[102,1610,1611,1613,1615],{"class":104,"line":133},[102,1612,137],{"class":136},[102,1614,140],{"class":112},[102,1616,143],{"class":136},[102,1618,1619,1621,1624],{"class":104,"line":146},[102,1620,137],{"class":136},[102,1622,1623],{"class":112},"sync",[102,1625,143],{"class":136},[102,1627,1628],{"class":104,"line":156},[102,1629,159],{"class":129},[102,1631,1632],{"class":104,"line":162},[102,1633,120],{"emptyLinePlaceholder":119},[102,1635,1636,1638,1641,1643,1646,1649,1651],{"class":104,"line":167},[102,1637,170],{"class":108},[102,1639,1640],{"class":112}," runConcurrently",[102,1642,200],{"class":129},[102,1644,1645],{"class":1159},"jobs",[102,1647,1648],{"class":129}," []",[102,1650,170],{"class":108},[102,1652,1653],{"class":129},"()) {\n",[102,1655,1656,1659,1662,1664,1666],{"class":104,"line":179},[102,1657,1658],{"class":108},"    var",[102,1660,1661],{"class":129}," wg ",[102,1663,1623],{"class":112},[102,1665,67],{"class":129},[102,1667,1668],{"class":112},"WaitGroup\n",[102,1670,1671,1674,1676],{"class":104,"line":186},[102,1672,1673],{"class":129},"    _ ",[102,1675,1343],{"class":108},[102,1677,1678],{"class":129}," wg\n",[102,1680,1681,1683,1685],{"class":104,"line":213},[102,1682,1673],{"class":129},[102,1684,1343],{"class":108},[102,1686,1687],{"class":129}," jobs\n",[102,1689,1690],{"class":104,"line":218},[102,1691,262],{"class":129},[102,1693,1694],{"class":104,"line":224},[102,1695,120],{"emptyLinePlaceholder":119},[102,1697,1698,1700,1702],{"class":104,"line":239},[102,1699,170],{"class":108},[102,1701,173],{"class":112},[102,1703,176],{"class":129},[102,1705,1706,1709,1711,1714,1716,1719,1722,1724,1726],{"class":104,"line":259},[102,1707,1708],{"class":129},"    done ",[102,1710,615],{"class":108},[102,1712,1713],{"class":112}," make",[102,1715,200],{"class":129},[102,1717,1718],{"class":108},"chan",[102,1720,1721],{"class":108}," int",[102,1723,1152],{"class":129},[102,1725,1576],{"class":203},[102,1727,159],{"class":129},[102,1729,1731],{"class":104,"line":1730},16,[102,1732,120],{"emptyLinePlaceholder":119},[102,1734,1736,1739,1742,1744],{"class":104,"line":1735},17,[102,1737,1738],{"class":112},"    runConcurrently",[102,1740,1741],{"class":129},"([]",[102,1743,170],{"class":108},[102,1745,1746],{"class":129},"(){\n",[102,1748,1750,1753,1756,1759,1761],{"class":104,"line":1749},18,[102,1751,1752],{"class":108},"        func",[102,1754,1755],{"class":129},"() { done ",[102,1757,1758],{"class":108},"\u003C-",[102,1760,1346],{"class":203},[102,1762,1763],{"class":129}," },\n",[102,1765,1767,1769,1771,1773,1776],{"class":104,"line":1766},19,[102,1768,1752],{"class":108},[102,1770,1755],{"class":129},[102,1772,1758],{"class":108},[102,1774,1775],{"class":203}," 2",[102,1777,1763],{"class":129},[102,1779,1781,1783,1785,1787,1790],{"class":104,"line":1780},20,[102,1782,1752],{"class":108},[102,1784,1755],{"class":129},[102,1786,1758],{"class":108},[102,1788,1789],{"class":203}," 3",[102,1791,1763],{"class":129},[102,1793,1795],{"class":104,"line":1794},21,[102,1796,1797],{"class":129},"    })\n",[102,1799,1801],{"class":104,"line":1800},22,[102,1802,120],{"emptyLinePlaceholder":119},[102,1804,1806,1808,1810,1812,1815],{"class":104,"line":1805},23,[102,1807,189],{"class":129},[102,1809,192],{"class":112},[102,1811,200],{"class":129},[102,1813,1814],{"class":112},"len",[102,1816,1817],{"class":129},"(done))\n",[102,1819,1821],{"class":104,"line":1820},24,[102,1822,262],{"class":129},[1423,1824,1825],{"v-slot:hints":80},[1426,1826,1827,1834,1841],{},[347,1828,1829,1830,1833],{},"В цикле вызывай ",[64,1831,1832],{},"wg.Add(1)"," перед запуском горутины",[347,1835,1836,1837,1840],{},"Внутри горутины сделай ",[64,1838,1839],{},"defer wg.Done()"," и вызови задачу",[347,1842,1843,1844],{},"После запуска всех задач вызови ",[64,1845,1846],{},"wg.Wait()",[1848,1849,1850],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html 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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":80,"searchDepth":116,"depth":116,"links":1852},[1853,1854,1855,1856,1857,1858,1861,1862,1863,1864,1865,1866],{"id":23,"depth":116,"text":24},{"id":40,"depth":116,"text":41},{"id":88,"depth":116,"text":66},{"id":314,"depth":116,"text":315},{"id":360,"depth":116,"text":361},{"id":382,"depth":116,"text":383,"children":1859},[1860],{"id":416,"depth":123,"text":417},{"id":438,"depth":116,"text":439},{"id":561,"depth":116,"text":562},{"id":587,"depth":116,"text":588},{"id":750,"depth":116,"text":751},{"id":910,"depth":116,"text":911},{"id":1410,"depth":116,"text":1411},1781458319169]