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 |
Мотивация
Зачем нужен этот проект?
Существующие модели параллелизма в основных языках программирования имеют явные недостатки:
| Язык | Модель параллелизма | Проблема |
|---|---|---|
| Rust | async/await + tokio | «заражение асинхронностью», окрашивание функций, крутая кривая обучения |
| Go | goroutine | Отсутствие типобезопасности, гонки данных сложно обнаружить |
| Python | asyncio | Ограничения GIL, окрашивание функций |
| JavaScript | Promise/async | Ад обратных вызовов, окрашивание функций |
Проблемы старого проекта (RFC-001)
Трёхуровневая архитектура параллелизма (L1/L2/L3), предложенная в RFC-001, имеет следующие проблемы:
| Проблема | Описание |
|---|---|
| Сложная ментальная модель | Три уровня абстракции L1/L2/L3 увеличивают когнитивную нагрузку |
| Избыточность аннотаций | Аннотации @block/@eager/@auto делают код шумным |
| Высокая сложность анализа | Анализ DAG по всей программе увеличивает время компиляции |
| Сложные ограничения типов | trait'ы Send/Sync увеличивают когнитивную нагрузку |
| Непредсказуемость | Автоматическое параллельное поведение трудно прогнозировать и отлаживать |
Цели проектирования
- Простота: только одна параллельная примитива (
spawn), способная модифицировать произвольное выражение - Явность: пользователь чётко знает, где параллелизм, а где последовательность
- Безопасность: правила владения естественно расширяются без дополнительных ограничений типов
- Контролируемость: нет неявного параллелизма, нет непреднамеренного параллельного поведения
- Синхронность: вызывающая сторона блокируется синхронно, без обратных вызовов и
await
Предложение
1. Природа блока {}: вычислительная единица на основе зависимостей
В YaoXiang {} — это вычислительная единица на основе зависимостей.
| Свойство | Описание |
|---|---|
| Управление зависимостями | При выполнении блок проверяет готовность всех внутренних переменных; если все готовы — выполняется немедленно, иначе блокируется в ожидании |
| Момент выполнения | Определяется зависимостями, не зависит от «немедленного» или «отложенного» |
| Возвращаемое значение | Явное return; без return по умолчанию возвращается Void |
| Единообразие синтаксиса | Семантика одинакова в теле функции, инициализации переменной или после spawn |
| Изоляция области видимости | Переменные строго ограничены внутренней областью {}, не «просачиваются» наружу |
// Пример управления зависимостями
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 настоящего документа.
// 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 создаёт независимую область видимости; внутренние переменные не влияют на внешнюю:
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):
data = load_data()
result = spawn {
process(data) // владение data перемещается в выражение spawn
}
// data здесь недоступен (перемещён)Если требуется разделение между несколькими задачами, используйте ref:
data = load_data()
shared = ref data // компилятор автоматически выбирает Rc или Arc
result = spawn {
process_a(shared), // разделяемая ссылка
process_b(shared) // разделяемая ссылка
}Разделение между итерациями: используйте ref для захвата во внешнюю область; итерации разделяют одну и ту же ссылку.
2.4 Правила распространения ошибок
spawn { a, b, c } (блок)
- Ожидание завершения всех задач (даже если некоторые уже завершились неудачно)
- Распространение первой встреченной ошибки
- Используйте
?для явной маркировки точек распространения ошибки
(a, b) = spawn {
fetch("url1")?, // может завершиться ошибкой
fetch("url2")? // может завершиться ошибкой
}
// если любая задача завершается ошибкой, всё выражение spawn распространяет первую ошибкуspawn for x in items { body? }
- Возвращает первую ошибку после завершения всех итераций
- После неудачной итерации оставшиеся итерации продолжают выполняться (не отменяются)
- Используйте
?для явной маркировки точек распространения ошибки
results = spawn for item in items {
process(item)? // ошибка любой итерации → ждём завершения всех → распространяем первую ошибку
}spawn while cond { body? }
Наследует собственную семантику ошибок while:
stepс?распространяет ошибку → весьspawn whileзавершается ошибкой, новая итерация не начинаетсяstepбез распространения ошибки (ошибка поглощается) → переход к следующей итерации
spawn while has_next() {
item = next() // без распространения ошибки, даже при сбое переходит к следующей итерации
process(item)
}spawn if c { a } else { b }
- Условие
cвычисляется последовательно - Ошибка при вычислении
c→ общая ошибка - Ошибка внутри выбранной ветви → общая ошибка
result = spawn if cond()? { // cond вычисляется последовательно, ошибка → общая ошибка
fetch_a()?
} else {
fetch_b()?
}2.5 Правила для типов ресурсов
Компилятор отслеживает использование типов ресурсов, обеспечивая безопасность параллелизма:
| Тип ресурса | Описание | Поведение компилятора |
|---|---|---|
FilePath | Путь файловой системы | Операции над одним путём автоматически сериализуются |
HttpUrl | HTTP-эндпоинт | Операции над одним URL автоматически сериализуются |
DBUrl | Соединение с БД | Операции над одним соединением автоматически сериализуются |
Console | Стандартный вывод | Все операции Console автоматически сериализуются |
Внутри блока spawn { ... }
// Операции с одним файлом автоматически сериализуются
(a, b) = spawn {
read_file("data.txt"), // выполняется сначала
write_file("data.txt", x) // ожидает завершения чтения
}Межитерационные операции с одним ресурсом в spawn for ... { ... }
Когда все итерации оперируют одним и тем же типом ресурса, компилятор автоматически понижает уровень до последовательного (spawn вырождается в последовательный for, без ошибок):
// Все итерации пишут в один и тот же путь к файлу → автоматически становится последовательным
results = spawn for item in items {
write_file("data.txt", item)
}
// компилятор автоматически сериализует все итерацииОбоснование проектирования: ключевое слово
spawnпо-прежнему выражает намерение параллелизма; при конфликте ресурсов компилятор автоматически понижает уровень, что лучше соответствует принципу минимального удивления, чем прямой отказ.
Захват &mut в spawn while ... { ... }
Ошибка на этапе компиляции: spawn while не позволяет захватывать внешние переменные типа &mut:
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 взаимоисключающее, выполняется максимум одна ветвь — конфликта параллелизма нет:
result = spawn if use_cache {
load_from_cache(key) // ветвь 1: чтение из кэша
} else {
fetch(key) // ветвь 2: чтение по URL
}2.6 Вложенный spawn
Выражения spawn могут быть вложенными; внутренний уровень создаёт независимую область параллелизма:
(a, b) = spawn {
x = spawn {
fetch("url1"),
fetch("url2")
},
y = compute(x)
}Семантика вложенности:
- Внутренний
spawn— независимая область параллелизма (независимая очередь задач, независимое распространение ошибок) - Внутренние ошибки независимо распространяются наружу (внешняя задача получает ошибку, ожидая завершения внутренней)
- Правила для типов ресурсов внутреннего уровня отслеживаются независимо (не объединяются с внешним уровнем)
// 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/Atomic | ref автоматически выбирает Rc/Arc |
| Трёхуровневая ментальная модель L1/L2/L3 | Обычный код последовательный, выражения spawn — параллельные |
| Окрашивание функций (async/await) | Нет окрашивания функций |
spawn модифицирует только блок {} | spawn модифицирует произвольное выражение (см. RFC-032) |
4. Правила возврата
Правила возврата в YaoXiang единообразны и ясны:
| Запись | Возвращаемое значение | Пояснение |
|---|---|---|
= expr (без фигурных скобок) | Возвращается expr напрямую | Выражение — это значение |
= { ... } (с фигурными скобками) | Требуется return, иначе возвращается Void | Блок требует явного возврата |
// Без фигурных скобок: прямой возврат
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, никаких странных аннотаций.
// Обычный код: последовательное выполнение
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)
}Компромиссы
Преимущества
- Простота: только одна параллельная примитива (
spawn), модифицирующая произвольное выражение - Явность: пользователь чётко знает, где параллелизм, а где последовательность; нет неявного параллелизма
- Безопасность: правила владения естественно расширяются без дополнительных ограничений типов вроде
Send/Sync - Контролируемость: нет автоматического параллельного поведения, что позволяет избежать непреднамеренных проблем параллелизма
- Синхронность: вызывающая сторона блокируется синхронно, код легко понять и отлаживать
- Нет окрашивания функций: отсутствует проблема окрашивания функций, свойственная async/await
- Эффективная компиляция: анализ DAG ограничен выражением
spawn, время компиляции контролируемо - Ортогональность:
spawnестественно сочетается с произвольными конструкциями потока управления (подробнее в RFC-032)
Недостатки
- Требуется явный
spawn: нет автоматического параллелизма; пользователь должен вручную отмечать точки параллелизации - Анализ DAG внутри выражения
spawn: компилятор должен выполнять анализ зависимостей внутри выраженияspawn - Несовместимость со старым кодом: код, использующий шаблоны старого RFC-001, требует миграции
Альтернативы
| Альтернатива | Почему не выбрана |
|---|---|
| Автоматический DAG по всей программе (RFC-001) | Высокая сложность, длительная компиляция, непредсказуемое поведение |
| async/await | Окрашивание функций, крутая кривая обучения, плохая читаемость кода |
| goroutine | Отсутствие типобезопасности, гонки данных сложно обнаружить |
| Модель акторов | Сложная передача сообщений, трудная отладка |
| CSP (каналы Go) | Отсутствие типобезопасности, взаимные блокировки сложно обнаружить |
spawn модифицирует только блок {} | Нарушает ортогональность, делает spawn for особым случаем (см. RFC-032) |
Стратегия реализации
Анализ на этапе компиляции
- Распознавание формы выражения: форма выражения после
spawnопределяет декомпозицию задач (подробнее в RFC-032 §Анализ DAG) - Построение DAG: анализ отношений зависимостей внутри выражения
spawn - Топологическая сортировка: определение порядка выполнения внутри выражения
spawn - Идентификация параллелизма: распознавание поддеревьев без зависимостей внутри выражения
spawn - Анализ побега:
ref→ Rc или Arc - Обнаружение конфликтов ресурсов: обнаружение потенциальных конфликтов типов ресурсов
Организация модулей
Код, связанный с 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 for | 2026-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/await | 2026-06-05 |
| Типы ресурсов | Встроенные + определяемые пользователем | Автоматическая сериализация | 2026-06-05 |
Ошибка spawn {} | Ждём завершения всех, распространяем первую ошибку | Детерминированное поведение | 2026-06-05 |
Ошибка spawn for | Ждём завершения всех, распространяем первую ошибку | Согласовано с spawn {} | 2026-07-04 |
Ошибка spawn while | Наследует семантику ошибок while | Стандартное поведение while | 2026-07-04 |
Ошибка условия spawn if | c вычисляется последовательно, ошибка → общая ошибка | Интуитивно понятно | 2026-07-04 |
Один ресурс в spawn for | Автоматическое понижение до последовательного | Безопасное понижение, без грубого отказа | 2026-07-04 |
Захват &mut в spawn while | Ошибка на этапе компиляции | Избежание гонок данных, без введения Sync | 2026-07-04 |
Один ресурс в spawn if | Допустимо без предупреждений | Взаимоисключающие ветви не создают конфликта | 2026-07-04 |
Вложенный spawn | Внутренний уровень — независимая область параллелизма | Независимые очереди задач, ошибки, ресурсы | 2026-07-04 |
Ссылки
Официальная документация YaoXiang
- Спецификация модели параллелизма
- RFC-001 Модель параллелизма (устаревший)
- RFC-008 Модель параллелизма во время выполнения
- RFC-009 Модель владения
- RFC-010 Унифицированный синтаксис типов
- RFC-011 Система обобщений
- RFC-032 унифицированный модификатор выражений spawn — рефакторинг AST/IR
Внешние ссылки
Жизненный цикл и местоположение
| Статус | Расположение | Пояснение |
|---|---|---|
| Принято (пересмотренная версия) | docs/design/rfc/accepted/ | Совместно с RFC-032 определяет spawn (семантика выполнения) |
