Открыть сервис

Корутины

Корутины — это программные компоненты, обобщающие понятие подпрограммы (функции), которые могут приостанавливать своё выполнение в определённых точках, сохраняя своё состояние (локальные переменные, счётчик команд и стек вызовов), и возобновлять выполнение с места остановки. В отличие от обычных функций, которые выполняются от начала до конца за один вызов, корутины позволяют реализовать кооперативную многозадачность, где передача управления между задачами происходит явно, без вмешательства операционной системы.

История и происхождение

Концепция корутин была впервые формализована в 1963 году Мелвином Конвеем в статье «Design of a Separable Transition-diagram Compiler». Конвей использовал корутины для организации работы компилятора, где лексический и синтаксический анализаторы обменивались управлением. В 1975 году Дональд Кнут включил корутины в свою книгу «Искусство программирования», описав их как «обобщение подпрограмм».

В 1980-х годах корутины были реализованы в языке Modula-2, а затем в языках семейства Simula и Smalltalk. Широкое распространение корутины получили в начале 2000-х годов с появлением языков Python (с версии 2.5), Lua, C# (с версии 2.0, ключевые слова yield и async/await), а также в JavaScript (с версии ES2017). В языке Go корутины реализованы в виде горутин (goroutines) — облегчённых потоков выполнения, управляемых рантаймом.

Отличие от потоков и процессов

Корутины принципиально отличаются от потоков (threads) и процессов (processes) по нескольким параметрам:

  • Управление: корутины управляются на уровне приложения (пользовательский режим), а потоки — операционной системой (системный режим). Переключение между корутинами не требует системных вызовов, что значительно быстрее.
  • Параллелизм: корутины выполняются в одном потоке, обеспечивая кооперативную многозадачность. Потоки могут выполняться параллельно на разных ядрах процессора, но требуют синхронизации доступа к общим данным.
  • Ресурсы: корутины используют значительно меньше памяти, чем потоки (типичный стек корутины — несколько килобайт, тогда как стек потока — мегабайты). Создание и переключение корутин обходится дешевле.
  • Состояние: корутины сохраняют своё состояние при приостановке, но не имеют собственного стека вызовов в традиционном смысле (в некоторых реализациях стек может быть частично сохранён в куче).

Виды корутин

Асимметричные (полу-корутины)

Асимметричные корутины, также называемые генераторами или полу-корутинами, передают управление только своему вызывающему коду. Они не могут вызывать другие корутины напрямую. Типичный пример — генераторы в Python (ключевое слово yield). Вызов yield приостанавливает корутину и возвращает значение вызывающей стороне, которая затем может возобновить выполнение.

Симметричные (полные корутины)

Симметричные корутины могут передавать управление любой другой корутине, образуя сеть взаимосвязанных компонентов. Они поддерживают произвольные переходы между корутинами, что позволяет реализовывать сложные сценарии, такие как конечные автоматы или кооперативные планировщики. Примеры: корутины в Lua, горутины в Go (хотя горутины часто рассматриваются как отдельная концепция).

Стековые и бесстековые

  • Стековые корутины (stackful coroutines) имеют собственный стек вызовов, который сохраняется при приостановке. Они могут вызывать вложенные функции, которые также могут приостанавливаться. Примеры: корутины в Lua, горутины в Go.
  • Бесстековые корутины (stackless coroutines) не имеют собственного стека; их состояние хранится в замыкании или структуре данных. Они не могут вызывать другие корутины внутри себя (кроме как через специальные механизмы, такие как async/await в C#). Примеры: генераторы в Python, корутины в C++ (через co_await).

Реализация в популярных языках программирования

Python

В Python корутины реализованы на основе генераторов (с версии 2.5) и ключевых слов async/await (с версии 3.5). Бесстековые корутины Python поддерживают асинхронное программирование с помощью цикла событий (event loop) из библиотеки asyncio. Пример:

```python import asyncio

async def fetch_data(): await asyncio.sleep(1) return "data"

async def main(): result = await fetch_data() print(result)

asyncio.run(main()) ```

C#

В C# корутины реализованы через ключевые слова async/await (с версии 5.0). Компилятор преобразует асинхронные методы в конечные автоматы, которые приостанавливаются при встрече await и возобновляются после завершения ожидаемой задачи. Пример:

``csharp async Task<int> GetResultAsync() { await Task.Delay(1000); return 42; } ``

JavaScript

В JavaScript корутины реализованы через async/await (с ES2017). Асинхронные функции возвращают промисы (Promise) и приостанавливаются на await. Пример:

``javascript async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); } ``

Go

В Go корутины реализованы как горутины (goroutines) — лёгкие потоки выполнения, запускаемые ключевым словом go. Горутины являются стековыми и управляются планировщиком рантайма Go. Они общаются через каналы (channels), что обеспечивает безопасную передачу данных без явной синхронизации. Пример:

``go func main() { ch := make(chan int) go func() { ch <- 42 }() result := <-ch fmt.Println(result) } ``

C++

В C++ корутины были стандартизированы в C++20. Они поддерживают как стековые, так и бесстековые реализации через ключевые слова co_await, co_yield и co_return. Пример:

```cpp

include <coroutine>

include <iostream>

struct Task { struct promise_type { Task get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; };

Task example() { std::cout << "Hello" << std::endl; co_await std::suspend_always{}; std::cout << "World" << std::endl; } ```

Применение

Асинхронное программирование

Корутины являются основой асинхронного ввода-вывода (I/O). Они позволяют писать неблокирующий код, который выглядит как синхронный, без сложных колбэков (callback hell). Примеры: веб-серверы на Python (aiohttp), C# (ASP.NET Core), JavaScript (Node.js).

Кооперативная многозадачность

В игровых движках (Unity, Unreal Engine) корутины используются для реализации сценариев с задержками, анимаций и последовательностей действий без блокировки основного потока.

Потоковая обработка данных

Генераторы (асимметричные корутины) применяются для обработки больших объёмов данных без загрузки всего набора в память. Пример: чтение файлов построчно, обработка потоков данных в реальном времени.

Компиляторы и парсеры

Корутины упрощают реализацию лексических анализаторов и синтаксических анализаторов, позволяя организовывать взаимную рекурсию между компонентами.

Критика и ограничения

  • Сложность отладки: корутины могут усложнять отладку из-за нелинейного потока выполнения и скрытого состояния.
  • Проблемы с исключениями: обработка исключений в корутинах может быть нетривиальной, особенно при вложенных вызовах.
  • Производительность: хотя переключение корутин дешевле переключения потоков, накладные расходы на создание и управление корутинами (особенно стековыми) могут быть значительными при большом количестве.
  • Совместимость: не все библиотеки и фреймворки корректно работают с корутинами, особенно в языках, где корутины были добавлены позже (например, C++).

Интересные факты

  • Термин «корутина» (coroutine) происходит от латинского «co-» (совместно) и «routina» (маршрут), что отражает совместное выполнение нескольких маршрутов.
  • В языке Lua корутины являются одной из ключевых особенностей, используемых в игровых движках (например, World of Warcraft использует Lua для скриптов).
  • В языке Go горутины настолько лёгкие, что типичное приложение может запускать миллионы горутин одновременно без значительного потребления памяти.
  • В 2020 году корутины в C++ были признаны одним из самых сложных для понимания нововведений языка из-за низкоуровневой реализации.

Источники

  • Conway, M. E. (1963). «Design of a Separable Transition-diagram Compiler».
  • Knuth, D. E. (1975). «The Art of Computer Programming, Volume 1: Fundamental Algorithms».
  • Moura, A. L., & Ierusalimschy, R. (2004). «Revisiting Coroutines».
  • Документация Python: «Coroutines and Tasks» (docs.python.org).
  • Документация C#: «Asynchronous programming with async and await» (docs.microsoft.com).
  • Документация Go: «Effective Go: Goroutines» (golang.org).
  • Стандарт C++20: «Coroutines (ISO/IEC 14882:2020)».

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →