Skip to content

RFC-024: Семантика параллельного выполнения на основе spawn ​

Данный документ определяет семантику поведения spawn во время выполнения. Синтаксическая ортогональность, рефакторинг AST/IR и расширения системы типов описаны в RFC-032.

Два RFC совместно определяют spawn — 024 отвечает на вопрос «что делать», 032 — «как представить».

Ссылки:

Резюме ​

Данный документ определяет семантику поведения во время выполнения ключевого слова spawn в языке программирования YaoXiang: spawn <expr> — это единственная параллельная примитива, способная модифицировать произвольное выражение; вызывающая сторона блокируется синхронно. Форма выражения определяет гранулярность декомпозиции задач; ядро выполнения осуществляет планирование по модели GMP — задачи без зависимостей попадают в рабочую очередь, worker'ы «перехватывают» их на конкурентной основе.

Основной замысел — одна примитива, один набор правил:

spawn <expr>               ← единственная параллельная примитива
декомпозиция задач определяется формой выражения  ← единственное правило
синхронное блокирование в ожидании результата      ← единственное поведение

Устранённая сложность:

  • ❌ Нет аннотаций @block/@eager/@auto
  • ❌ Нет trait'ов Send/Sync
  • ❌ Нет Mutex/RwLock/Atomic
  • ❌ Нет future/неблокирующих дескрипторов
  • ❌ Нет анализа DAG по всей программе
  • ❌ Нет «окрашивания функций» (async/await)

Пользовательская ментальная модель: обычный код, который вы пишете, выполняется последовательно. Когда вы хотите, чтобы несколько дел выполнялись одновременно, поместите их в spawn <expr>. Никаких обратных вызовов, никаких await, никаких странных аннотаций.

Источники проектирования ​

ДокументСвязь
RFC-001Заменён настоящим документом
RFC-008Архитектура ядра выполнения, ортогональна
RFC-009Модель владения, без изменений
RFC-010Унифицированный синтаксис типов
RFC-032Рефакторинг AST/IR, совместно определяет spawn

Мотивация ​

Зачем нужен этот проект? ​

Существующие модели параллелизма в основных языках программирования имеют явные недостатки:

ЯзыкМодель параллелизмаПроблема
Rustasync/await + tokio«заражение асинхронностью», окрашивание функций, крутая кривая обучения
GogoroutineОтсутствие типобезопасности, гонки данных сложно обнаружить
PythonasyncioОграничения GIL, окрашивание функций
JavaScriptPromise/asyncАд обратных вызовов, окрашивание функций

Проблемы старого проекта (RFC-001) ​

Трёхуровневая архитектура параллелизма (L1/L2/L3), предложенная в RFC-001, имеет следующие проблемы:

ПроблемаОписание
Сложная ментальная модельТри уровня абстракции L1/L2/L3 увеличивают когнитивную нагрузку
Избыточность аннотацийАннотации @block/@eager/@auto делают код шумным
Высокая сложность анализаАнализ DAG по всей программе увеличивает время компиляции
Сложные ограничения типовtrait'ы Send/Sync увеличивают когнитивную нагрузку
НепредсказуемостьАвтоматическое параллельное поведение трудно прогнозировать и отлаживать

Цели проектирования ​

  1. Простота: только одна параллельная примитива (spawn), способная модифицировать произвольное выражение
  2. Явность: пользователь чётко знает, где параллелизм, а где последовательность
  3. Безопасность: правила владения естественно расширяются без дополнительных ограничений типов
  4. Контролируемость: нет неявного параллелизма, нет непреднамеренного параллельного поведения
  5. Синхронность: вызывающая сторона блокируется синхронно, без обратных вызовов и await

Предложение ​

1. Природа блока {}: вычислительная единица на основе зависимостей ​

В YaoXiang {} — это вычислительная единица на основе зависимостей.

СвойствоОписание
Управление зависимостямиПри выполнении блок проверяет готовность всех внутренних переменных; если все готовы — выполняется немедленно, иначе блокируется в ожидании
Момент выполненияОпределяется зависимостями, не зависит от «немедленного» или «отложенного»
Возвращаемое значениеЯвное return; без return по умолчанию возвращается Void
Единообразие синтаксисаСемантика одинакова в теле функции, инициализации переменной или после spawn
Изоляция области видимостиПеременные строго ограничены внутренней областью {}, не «просачиваются» наружу
yaoxiang
// Пример управления зависимостями
x = compute_x()        // x готов
y = compute_y()        // y готов
result = {
    // Зависит от x и y; выполняется немедленно, когда оба готовы
    return x + y
}

2. Семантика выражения spawn ​

spawn <expr> — это единственная параллельная примитива в YaoXiang. Может модифицировать произвольное выражение; форма выражения определяет гранулярность декомпозиции задач.

2.1 Правила создания задач ​

Форма выраженияДекомпозиция задачСемантика синхронизации
spawn { a, b, c }Непосредственные подвыражения → N независимых задачОжидание завершения всех задач
spawn for x in items { body }Каждая итерация → 1 задачаОжидание завершения всех итераций
spawn while cond { body }Каждая итерация цикла → 1 задача (между итерациями — по условию)Ожидание, пока условие false
spawn if c { a } else { b }Условие c вычисляется последовательно; выбранная ветвь целиком → 1 задачаОжидание завершения выбранной ветви
spawn call(x)Сам вызов → 1 задачаОжидание завершения вызова
spawn expr (произвольное выражение)Само выражение → 1 задачаОжидание завершения выражения

Мотивация проектирования: почему spawn может модифицировать произвольное выражение? Подробнее см. RFC-032 §Основной замысел.

Ортогональность потока управления: различия в семантике между spawn <expr> (spawn впереди) и <expr> spawn { body } (spawn после) описаны в RFC-032 §Ортогональность потока управления (основное определение). Поведение во время выполнения для всех «обратных» комбинаций (for ... spawn { } / while ... spawn { } / if ... spawn { }) — распространение ошибок, типы ресурсов, правила вложенности — наследует правила из §2.4 / §2.5 / §2.6 настоящего документа.

yaoxiang
// spawn блок: непосредственные подвыражения параллельны
(a, b) = spawn {
    t1 = fetch("url1")   // непосредственное подвыражение → параллельная задача 1
    t2 = fetch("url2")   // непосредственное подвыражение → параллельная задача 2
    return (t1, t2)      // явное возвращение кортежа
}

// spawn for: каждая итерация параллельна
results = spawn for item in items {
    process(item)        // каждая итерация → независимая задача
}

// spawn while: каждая итерация цикла параллельна
spawn while has_next() {
    step()               // каждая итерация цикла → независимая задача
}

// spawn if: выбранная ветвь целиком как задача
result = spawn if cond {
    branch_a()
} else {
    branch_b()
}

2.2 Изоляция области видимости ​

Выражение spawn создаёт независимую область видимости; внутренние переменные не влияют на внешнюю:

yaoxiang
x = 10
result = spawn {
    x = 20              // это локальный x внутри выражения spawn
    compute(x)
}
// x по-прежнему равен 10

result = spawn for item in items {
    item = item + 1     // итерационно-локальный item, независимая копия для каждой итерации
    process(item)
}
// внешний item не затронут

Итерационная переменная (например, x в for) — независимая копия для каждой итерации, автоматически уничтожается после завершения итерации.

2.3 Правила владения ​

После того как переменная попадает в выражение spawn, её нельзя использовать снаружи (семантика Move):

yaoxiang
data = load_data()
result = spawn {
    process(data)       // владение data перемещается в выражение spawn
}
// data здесь недоступен (перемещён)

Если требуется разделение между несколькими задачами, используйте ref:

yaoxiang
data = load_data()
shared = ref data       // компилятор автоматически выбирает Rc или Arc

result = spawn {
    process_a(shared),  // разделяемая ссылка
    process_b(shared)   // разделяемая ссылка
}

Разделение между итерациями: используйте ref для захвата во внешнюю область; итерации разделяют одну и ту же ссылку.

2.4 Правила распространения ошибок ​

spawn { a, b, c } (блок) ​
  1. Ожидание завершения всех задач (даже если некоторые уже завершились неудачно)
  2. Распространение первой встреченной ошибки
  3. Используйте ? для явной маркировки точек распространения ошибки
yaoxiang
(a, b) = spawn {
    fetch("url1")?,     // может завершиться ошибкой
    fetch("url2")?      // может завершиться ошибкой
}
// если любая задача завершается ошибкой, всё выражение spawn распространяет первую ошибку
spawn for x in items { body? } ​
  • Возвращает первую ошибку после завершения всех итераций
  • После неудачной итерации оставшиеся итерации продолжают выполняться (не отменяются)
  • Используйте ? для явной маркировки точек распространения ошибки
yaoxiang
results = spawn for item in items {
    process(item)?      // ошибка любой итерации → ждём завершения всех → распространяем первую ошибку
}
spawn while cond { body? } ​

Наследует собственную семантику ошибок while:

  • step с ? распространяет ошибку → весь spawn while завершается ошибкой, новая итерация не начинается
  • step без распространения ошибки (ошибка поглощается) → переход к следующей итерации
yaoxiang
spawn while has_next() {
    item = next()       // без распространения ошибки, даже при сбое переходит к следующей итерации
    process(item)
}
spawn if c { a } else { b } ​
  • Условие c вычисляется последовательно
  • Ошибка при вычислении c → общая ошибка
  • Ошибка внутри выбранной ветви → общая ошибка
yaoxiang
result = spawn if cond()? {  // cond вычисляется последовательно, ошибка → общая ошибка
    fetch_a()?
} else {
    fetch_b()?
}

2.5 Правила для типов ресурсов ​

Компилятор отслеживает использование типов ресурсов, обеспечивая безопасность параллелизма:

Тип ресурсаОписаниеПоведение компилятора
FilePathПуть файловой системыОперации над одним путём автоматически сериализуются
HttpUrlHTTP-эндпоинтОперации над одним URL автоматически сериализуются
DBUrlСоединение с БДОперации над одним соединением автоматически сериализуются
ConsoleСтандартный выводВсе операции Console автоматически сериализуются
Внутри блока spawn { ... } ​
yaoxiang
// Операции с одним файлом автоматически сериализуются
(a, b) = spawn {
    read_file("data.txt"),      // выполняется сначала
    write_file("data.txt", x)   // ожидает завершения чтения
}
Межитерационные операции с одним ресурсом в spawn for ... { ... } ​

Когда все итерации оперируют одним и тем же типом ресурса, компилятор автоматически понижает уровень до последовательного (spawn вырождается в последовательный for, без ошибок):

yaoxiang
// Все итерации пишут в один и тот же путь к файлу → автоматически становится последовательным
results = spawn for item in items {
    write_file("data.txt", item)
}
// компилятор автоматически сериализует все итерации

Обоснование проектирования: ключевое слово spawn по-прежнему выражает намерение параллелизма; при конфликте ресурсов компилятор автоматически понижает уровень, что лучше соответствует принципу минимального удивления, чем прямой отказ.

Захват &mut в spawn while ... { ... } ​

Ошибка на этапе компиляции: spawn while не позволяет захватывать внешние переменные типа &mut:

yaoxiang
iter = make_iter()
spawn while iter.has_next() {       // ошибка компиляции
    item = iter.next()              // iter имеет тип &mut; разделяемая мутабельность между итерациями = гонка данных
}

Без повторного введения trait'а Sync: соответствует обещанию RFC-024 «без Send/Sync». Требуется, чтобы пользователь переключился на ref или на последовательную форму записи.

Один ресурс в обеих ветвях spawn if c { ... } else { ... } ​

Допустимо без предупреждений: условие if взаимоисключающее, выполняется максимум одна ветвь — конфликта параллелизма нет:

yaoxiang
result = spawn if use_cache {
    load_from_cache(key)            // ветвь 1: чтение из кэша
} else {
    fetch(key)                      // ветвь 2: чтение по URL
}

2.6 Вложенный spawn ​

Выражения spawn могут быть вложенными; внутренний уровень создаёт независимую область параллелизма:

yaoxiang
(a, b) = spawn {
    x = spawn {
        fetch("url1"),
        fetch("url2")
    },
    y = compute(x)
}

Семантика вложенности:

  • Внутренний spawn — независимая область параллелизма (независимая очередь задач, независимое распространение ошибок)
  • Внутренние ошибки независимо распространяются наружу (внешняя задача получает ошибку, ожидая завершения внутренней)
  • Правила для типов ресурсов внутреннего уровня отслеживаются независимо (не объединяются с внешним уровнем)
yaoxiang
// spawn for, вложенный в spawn while
results = spawn for x in items {
    inner = spawn while has_more(x) {
        step(x)
    }
    process(inner)
}

3. Разрыв со старым проектом ​

Старый проект (RFC-001)Новый проект (RFC-024 + RFC-032)
Автоматический анализ DAG по всей программеАнализ только внутри выражения spawn
Аннотации @block/@eager/@autoНет аннотаций, управление по зависимостям
trait'ы Send/SyncНе нужны, владение + ref обрабатывают автоматически
future/неблокирующие дескрипторыСинхронная блокировка, без обратных вызовов
Mutex/RwLock/Atomicref автоматически выбирает Rc/Arc
Трёхуровневая ментальная модель L1/L2/L3Обычный код последовательный, выражения spawn — параллельные
Окрашивание функций (async/await)Нет окрашивания функций
spawn модифицирует только блок {}spawn модифицирует произвольное выражение (см. RFC-032)

4. Правила возврата ​

Правила возврата в YaoXiang единообразны и ясны:

ЗаписьВозвращаемое значениеПояснение
= expr (без фигурных скобок)Возвращается expr напрямуюВыражение — это значение
= { ... } (с фигурными скобками)Требуется return, иначе возвращается VoidБлок требует явного возврата
yaoxiang
// Без фигурных скобок: прямой возврат
add: (a: Int, b: Int) -> Int = a + b

// С фигурными скобками: требуется return
process: (data: Data) -> Result = {
    validated = validate(data)?
    return ok(transform(validated))
}

// С фигурными скобками, но без return: возвращается Void
log: (message: String) -> Void = {
    print(message)  // без return, возвращается Void
}

5. Пользовательская ментальная модель ​

Обычный код, который вы пишете, выполняется последовательно.

Когда вы хотите, чтобы несколько дел выполнялись одновременно, поместите их в spawn <expr>.

Форма выражения определяет, как задачи разбиваются: каждое непосредственное подвыражение в блоке параллельно; каждая итерация for параллельна; выбранная ветвь if — это одна задача.

Всё выражение spawn блокирует выполнение синхронно, ожидая завершения всех задач.

Никаких обратных вызовов, никаких await, никаких странных аннотаций.

yaoxiang
// Обычный код: последовательное выполнение
a = compute_a()         // выполняется сначала
b = compute_b(a)        // зависит от a; выполняется после завершения a
c = compute_c(b)        // зависит от b; выполняется после завершения b

// Когда нужен параллелизм: используйте spawn
(x, y, z) = spawn {
    fetch("url1"),      // параллельно
    fetch("url2"),      // параллельно
    fetch("url3")       // параллельно
}
// ждём завершения всех, затем продолжаем
process(x, y, z)

// Параллелизм данных: spawn for
results = spawn for item in items {
    process(item)
}

Компромиссы ​

Преимущества ​

  1. Простота: только одна параллельная примитива (spawn), модифицирующая произвольное выражение
  2. Явность: пользователь чётко знает, где параллелизм, а где последовательность; нет неявного параллелизма
  3. Безопасность: правила владения естественно расширяются без дополнительных ограничений типов вроде Send/Sync
  4. Контролируемость: нет автоматического параллельного поведения, что позволяет избежать непреднамеренных проблем параллелизма
  5. Синхронность: вызывающая сторона блокируется синхронно, код легко понять и отлаживать
  6. Нет окрашивания функций: отсутствует проблема окрашивания функций, свойственная async/await
  7. Эффективная компиляция: анализ DAG ограничен выражением spawn, время компиляции контролируемо
  8. Ортогональность: spawn естественно сочетается с произвольными конструкциями потока управления (подробнее в RFC-032)

Недостатки ​

  1. Требуется явный spawn: нет автоматического параллелизма; пользователь должен вручную отмечать точки параллелизации
  2. Анализ DAG внутри выражения spawn: компилятор должен выполнять анализ зависимостей внутри выражения spawn
  3. Несовместимость со старым кодом: код, использующий шаблоны старого RFC-001, требует миграции

Альтернативы ​

АльтернативаПочему не выбрана
Автоматический DAG по всей программе (RFC-001)Высокая сложность, длительная компиляция, непредсказуемое поведение
async/awaitОкрашивание функций, крутая кривая обучения, плохая читаемость кода
goroutineОтсутствие типобезопасности, гонки данных сложно обнаружить
Модель акторовСложная передача сообщений, трудная отладка
CSP (каналы Go)Отсутствие типобезопасности, взаимные блокировки сложно обнаружить
spawn модифицирует только блок {}Нарушает ортогональность, делает spawn for особым случаем (см. RFC-032)

Стратегия реализации ​

Анализ на этапе компиляции ​

  1. Распознавание формы выражения: форма выражения после spawn определяет декомпозицию задач (подробнее в RFC-032 §Анализ DAG)
  2. Построение DAG: анализ отношений зависимостей внутри выражения spawn
  3. Топологическая сортировка: определение порядка выполнения внутри выражения spawn
  4. Идентификация параллелизма: распознавание поддеревьев без зависимостей внутри выражения spawn
  5. Анализ побега: ref → Rc или Arc
  6. Обнаружение конфликтов ресурсов: обнаружение потенциальных конфликтов типов ресурсов

Организация модулей ​

Код, связанный с spawn, единообразно размещается в frontend/core/spawn/:

frontend/core/spawn/
├── mod.rs           # точка входа модуля spawn
├── placement.rs     # проверка допустимости позиции появления spawn
└── analysis.rs      # распознавание задач, анализ зависимостей, обнаружение конфликтов ресурсов

Примечание о миграции (2026-06-11): существующий frontend/core/typecheck/passes/spawn_placement.rs будет перенесён в frontend/core/spawn/placement.rs. Объявление модуля spawn_placement в каталоге typecheck/passes/ должно быть удалено синхронно.

Выполнение во время выполнения ​

Ссылаясь на архитектуру Runtime из RFC-008:

  • Embedded Runtime: без поддержки spawn, немедленное выполнение
  • Standard Runtime: поддержка выражений spawn
  • Full Runtime: Standard + балансировка нагрузки WorkStealer

Зависимости ​

  • RFC-008 (архитектура Runtime) → завершён
  • RFC-009 (модель владения) → завершён
  • RFC-010 (унифицированный синтаксис типов) → завершён
  • RFC-011 (система обобщений) → завершён
  • RFC-032 (рефакторинг AST/IR) → совместно с настоящим документом определяет spawn

Журнал проектных решений ​

РешениеРешениеПричинаДата
Параллельная примитиваspawn <expr>Простота, явность, контролируемость2026-06-05
Область модификации spawnПроизвольное выражениеСинтаксическая ортогональность, устранение специализации spawn for2026-07-04
Декомпозиция задачОпределяется формой выраженияВыразительность, единые правила2026-07-04
Модель выполненияСинхронная блокировкаПростота понимания и отладки2026-06-05
Область анализа DAGТолько внутри выражения spawnЭффективная компиляция, контролируемое поведение2026-06-05
Механизм разделенияref автоматически выбирает Rc/ArcУпрощение решений пользователя2026-06-05
АннотацииНетУменьшение шума в коде2026-06-05
Send/SyncУдаленыВладения + ref достаточно2026-06-05
Mutex/RwLockУдаленыref обрабатывает автоматически2026-06-05
future/дескрипторУдаленыСинхронная блокировка проще2026-06-05
Окрашивание функцийНетИзбежание проблем async/await2026-06-05
Типы ресурсовВстроенные + определяемые пользователемАвтоматическая сериализация2026-06-05
Ошибка spawn {}Ждём завершения всех, распространяем первую ошибкуДетерминированное поведение2026-06-05
Ошибка spawn forЖдём завершения всех, распространяем первую ошибкуСогласовано с spawn {}2026-07-04
Ошибка spawn whileНаследует семантику ошибок whileСтандартное поведение while2026-07-04
Ошибка условия spawn ifc вычисляется последовательно, ошибка → общая ошибкаИнтуитивно понятно2026-07-04
Один ресурс в spawn forАвтоматическое понижение до последовательногоБезопасное понижение, без грубого отказа2026-07-04
Захват &mut в spawn whileОшибка на этапе компиляцииИзбежание гонок данных, без введения Sync2026-07-04
Один ресурс в spawn ifДопустимо без предупрежденийВзаимоисключающие ветви не создают конфликта2026-07-04
Вложенный spawnВнутренний уровень — независимая область параллелизмаНезависимые очереди задач, ошибки, ресурсы2026-07-04

Ссылки ​

Официальная документация YaoXiang ​

Внешние ссылки ​


Жизненный цикл и местоположение ​

СтатусРасположениеПояснение
Принято (пересмотренная версия)docs/design/rfc/accepted/Совместно с RFC-032 определяет spawn (семантика выполнения)