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 endpoint | Операции с одним 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 по-прежнему выражает намерение параллелизма; автоматическое понижение компилятором при конфликте ресурсов — более соответствует принципу наименьшего удивления, чем прямой отказ.
spawn while ... { ... } захватывает &mut
Ошибка времени компиляции: spawn while не позволяет захватывать внешние переменные типа &mut:
iter = make_iter()
spawn while iter.has_next() { // Ошибка времени компиляции
item = iter.next() // iter имеет тип &mut, общий доступ с изменением между итерациями = гонка данных
}Не повторное введение trait
Sync: Соответствует обещанию RFC-024 "без Send/Sync". Требуется от пользователя использоватьrefили написать без spawn.
spawn if c { ... } else { ... } обе ветви с одним ресурсом
Легально без предупреждений: Условия if взаимоисключающие, выполняется максимум одна ветвь, конфликта параллелизма не существует:
result = spawn if use_cache {
load_from_cache(key) // Ветвь 1: чтение из cache
} else {
fetch(key) // Ветвь 2: чтение из URL
}2.6 Вложенный spawn
Выражения spawn могут быть вложенными; внутренний spawn создаёт независимую параллельную область:
(a, b) = spawn {
x = spawn {
fetch("url1"),
fetch("url2")
},
y = compute(x)
}Семантика вложенности:
- Внутренний spawn — независимая параллельная область (независимая очередь задач, независимое распространение ошибок)
- Внутренние ошибки распространяются независимо во внешнюю область (внешняя задача получает ошибку при ожидании завершения внутренней)
- Правила типов ресурсов внутреннего 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 | Отсутствие безопасности типов, трудно обнаружить гонки данных |
| Модель Actor | Сложность передачи сообщений, трудности отладки |
| 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 |
spawn while захватывает &mut | Ошибка времени компиляции | Избежание гонок данных, без введения Sync | 2026-07-04 |
spawn if с одним ресурсом | Легально без предупреждений | Взаимоисключающие ветви не создают конфликта | 2026-07-04 |
| Вложенный spawn | Внутренний — независимая параллельная область | Независимые очереди задач, ошибки, ресурсы | 2026-07-04 |
Библиографические ссылки
Официальная документация YaoXiang
- Спецификация модели параллелизма
- RFC-001 Модель параллелизма (устарела)
- RFC-008 Модель параллелизма Runtime
- RFC-009 Модель владения
- RFC-010 Унифицированный синтаксис типов
- RFC-011 Обобщённая система
- RFC-032 Унифицированный модификатор выражений spawn — рефакторинг AST/IR
Внешние ссылки
Жизненный цикл и судьба
| Статус | Расположение | Пояснение |
|---|---|---|
| Принято (пересмотренная версия) | docs/design/rfc/accepted/ | Совместно с RFC-032 определяет spawn (семантика времени выполнения) |
