Співпрограми (корутіни, coroutine) - що це stack overflow російською

Співпрограми (корутіни, coroutine) - це потоки виконання коду, які організовуються поверх апаратних (системних) потоків.
Потік виконання коду - це послідовність операцій, які виконуються один за одним. У потрібні моменти ця послідовність може бути припинена, і замість неї може почати виконуватися частина іншої послідовності операцій.

Системні потоки складаються з інструкцій процесора, і на одному ядрі процесора можуть по черзі працювати кілька системних потоків.
Співпрограми працюють на більш високому рівні - кілька співпрограми можуть по черзі виконувати свій код на одному системному потоці. (В залежності від реалізації, співпрограми може бути не прив'язана до конкретного системного потоку, а наприклад виконувати свій код на пулі потоків).

На відміну від системних потоків, які перемикаються системою в довільні моменти часу (витісняє багатозадачність), співпрограми переключаються вручну, в місцях, зазначених програмістом (кооперативна багатозадачність).

Позначимо операції над співпрограми наступним чином:

  • handle = spawn (СП); - запуск співпрограми,
  • yield; - припинення поточної співпрограми,
  • resume (handle); - відновлення співпрограми.

Візьмемо дві співпрограми:

Тоді, якщо на одному системному потоці запустити СП1, а потім СП2, то системний потік виконує операції в такому наступному детерминированном порядку:

Так як обидві співпрограми виконуються на одному системному потоці, то в них неможливі гонки даних, код між yield виконується атомарному (в межах цього системного потоку).

Якщо ж запустити кожну співпрограми в окремому системному потоці, то тоді вони нічим не відрізнятимуться від звичайних системних потоків - будуть перемикатися там, де скаже система, необхідна синхронізація, щоб не було гонок даних.

асинхронні операції

Один з основних сценаріїв застосування співпрограми - це асинхронні операції, такі як введення-виведення і анімації в UI. Після початку асинхронної операції, співпрограми може зробити yield і продовжитися вже після завершення цієї операції. При цьому системний потік, на якому вона виконувалася, не засинає разом з співпрограми, а залишається вільний для інших співпрограми.

Традиційний асинхронний код має на увазі використання коллбеков, як параметрів функції, так і разом з future / promise:

Де callback - це функція, яка буде викликана після закінчення асинхронної записи. Як коллбека можна використовувати лямбда-функцію, наприклад, в синтаксисі С ++:

Введемо операцію handle = get_coroutine_handle () ;. яка буде видавати хендл (контекст) співпрограми в коді самої співпрограми.
Тоді всередині співпрограми можна написати:

Для зручності, в багатьох мовах використовують оператор await. який скорочує цей код до

Докладніший приклад:

Генератори

Інший сценарій використання співпрограми - це "генератори", співпрограми, які генерують послідовності однотипних об'єктів, наприклад, послідовності чисел:

Тут yield крім зупинки генератора також приймає значення, які видаються генератором.

Stackful реалізації співпрограми

Як випливає з назви, stackful (стекові) співпрограми - це співпрограми, у яких є свій стек, як і у системних потоків. Основна відмінність таких співпрограми від звичайних системних потоків - це те, що вони переключаються вручну. У порівнянні зі швидкістю перемикання системних потоків, переключення співпрограми практично безкоштовно (сотні тактів процесора). Однак, через те що для кожної співпрограми треба виділяти окремий стек та інші службові структури даних - їх створення і існування не дешевше, ніж створення системного потоку.

У Windows stackful співпрограми вбудовані в систему і називаються Fibers (Фібер, волокна). Волокна прив'язані до того системному потоку, на якому вони створені, перемикання (yield / resume) реалізується через функцію SwitchToFiber (fiber_handle).

Запуск stackful співпрограми нічим не відрізняється від запуску звичайного потоку, і може виглядати, наприклад, так:

Наявність власного стека дозволяє робити yield з вкладених викликів функцій.
Однак, тому що stackful співпрограми досить дороги, їх не можна використовувати для створення простих генераторів.

Stackless реалізації співпрограми

Stackless (безстековие) співпрограми ніяк не залежать від операційної системи, і реалізуються виключно засобами компілятора: код співпрограми переписується в об'єкт-кінцевий автомат, локальні змінні виділяються не на стеку, а стають членами цього об'єкта.

На відміну від stackful співпрограми, в співпрограми без стека yield може бути тільки в її тілі. Не можна зробити yield з функції, викликаної співпрограми.

Створення безстековой співпрограми - це створення об'єкта, що зберігає її стан (__fib), і зазвичай виглядає як виклик функції:

@ixSci, так, в деякому сенсі можна сказати "співпрограми не залежить від наявності потоків", але взагалі це звучить як "програма не залежить від наявності ЦПУ". Я написав про системні потоках бо це важливо в контексті проблем синхронізації - якщо співпрограми виконуються на різних системних потоках, то гонки даних можливі, якщо виконуються на одному - то перегонів не буде. - Abyx 22 Лютого '16 о 18:36

корутіни, асінкі та інший непотріб, це метод розробки при якому вдається позбутися генерації ниток і процесів (у запобігання усіляких перемикань і ініціалізацій), зберігаючи псевдопараллельное виконання коду. В основі лежить сіскол select / poll поверх якого накручено тонни цукрових нетрів, які виконують той же самий код в одному потоці граючи на операціях IO для перемикання між так званими корутінамі (тобішь звичайні функції). Такі справи ;)