Почему в Go нет гарантии порядка выполнения горутин
Что проверяют
Понимаете ли вы, что порядок выполнения это не свойство планировщика, а то, что задаётся явно. Заодно смотрят, не ждёте ли вы от go f() немедленного запуска и знаете ли, чем вообще подкреплены гарантии.
Как отвечать
Планировщик Go вытесняющий и работает по схеме M:N: горутины раскладываются по логическим процессорам, число которых задаёт GOMAXPROCS, а те исполняются на потоках ОС. Очереди у процессоров локальные, свободный ворует работу у соседа, плюс есть асинхронное вытеснение по сигналу. Момент, когда горутину снимут с процессора, код не контролирует. Сам go f() только ставит горутину в очередь, вызывающая сторона идёт дальше.
Отсюда вывод: программа, которая опирается на порядок запуска, опирается на удачу. Порядок задают средствами синхронизации, и их гарантии перечислены в модели памяти Go:
- отправка в канал происходит раньше завершения соответствующего приёма;
wg.Done()раньше возврата изwg.Wait();mu.Unlock()раньше следующего успешногоmu.Lock();once.Do(f)раньше возврата из любого другого вызова того жеonce.Do.
Если детерминированный порядок действительно нужен, его выстраивают руками: цепочка каналов, семафор на один слот, либо сбор результатов в слайс по индексу и сортировка уже после Wait. Полезно добавить, что проверяется всё это прогоном go test -race, и что гонка данных это не редкий сбой, а неопределённое поведение.
Где спотыкаются
Выходят из main, не дождавшись горутин. Завершение главной горутины гасит процесс целиком, поэтому «половина задач не отработала» это не планировщик, а отсутствующие WaitGroup или канал завершения.
Второе: считают, что небуферизованный канал упорядочивает всё. Он синхронизирует только конкретную пару отправки и приёма на себе. Какая из трёх пишущих горутин успеет первой, канал не решает.
Третье: расставляют time.Sleep, чтобы «дать горутине стартовать». Это не синхронизация, а подгонка под текущую машину: на загруженном сервере или под гонко-детектором тайминг другой, и тест начинает мигать.