Корутины в Go
Пока идет бурное обсуждение по поводу zero
, Расс Кокс продолжает свои рассуждения о хранении данных внутри горутины-потока исполнения. В этот раз он рассматривает корутины, как вариант генераторов/итераторов.
Вообще роль корутин (т.е. сопрограмм) в Go с самого момента появления языка выполняли горутины - почти все изучая Go пишут что-то вроде func generate(num int) <-chan int { ... }
для генерации списка чисел. Однако Расс утверждает, что в языке есть место и для корутин.
В общей терминологии это звучит так:
- Горутины нужны тогда, когда нужно асинхронное, параллельное исполнение. Две горутины могут исполнятся одновременно, независимо, им не нужно явно принимать и отдавать управление. Управлением горутинами, в общем случае, занимается рантайм.
- Корутины нужны тогда, когда нужно асинхронное, последовательное исполнение. Только одна корутина исполняется в момент времени, отдавая и принимая обратно управление в специальных точках-функциях (yield). Вызывающий код сам решает, в какой момент времени происходит переключение между корутинами.
Используя каналы с горутинами можно получить частичное поведение корутин — горутину можно заблокировать отправив данные другой горутине через канал и ожидая от нее ответ по второму каналу. Более того, полную подобную реализацию корутин Расс приводит в статье, показывая, что их можно реализовать как библиотечные функции и без новых языковых конструкций. Полная версия кода показывает вариант с ранним выходом и передачей паник вызывающей стороне.
Однако у всего это кода есть один существенный минус - скорость его исполнения. На MacBook Pro 2019 года один вызов yield
занимает порядка 380ns, что очень долго если говорить про итерацию по коллекциям или относительно простые арифметические операции. Однако имея доступ к внутренностям рантайма и подправив несколько мест в нем (бонусы бытия техлидом Go 😄) специально для этого случая, Рассу удалось достигнуть затрат в 40ns на одну итерацию, что в 10 раз быстрее изначальной реализации и уже достаточно быстро для использования подобного паттерна в общем случае.
От себя добавлю, что вопрос использования каналов как инструмента общения последовательных потоков управления поднимался несколько раз - тот же генератор, сигнатура которого приведена сверху, не требует, в своей сути, параллельного исполнения: большая часть времени тратится на синхронизацию данных между двумя горутинами через канал. Однако вариант однопоточных каналов авторы языка не стали рассматривать, т.к. в таком случае появлялась бы еще одна синтаксическая конструкция, которая нужна в достаточно небольшом числе сценариев (по отношению к общему), а вот случаи ее неправильного использования приведут к трудноуловимым гонкам данных.
Я рекомендую посмотреть код в самой статье, даже если вы плохо знаете английский - это хорошая гимнастика для мозгов с использованием каналов как двунаправленного средства обмена данными.
>>Click here to continue<<