⚠️ Устаревший (DEPRECATED)
Данный RFC заменён RFC-024:Новая модель конкурентности.
Трёхуровневая архитектура конкурентности (L1/L2/L3), аннотации @block/@eager, автоматический DAG-анализ и другие концепции RFC-001 были удалены. Новая модель использует блоки
spawn {}как единственный примитив параллелизма без аннотаций.Документ сохранён только для исторической справки.
title: "RFC-001:Модель спавна и система обработки ошибок"
RFC-001:Модель спавна и система обработки ошибок
Источники дизайна
| Документ | Связь |
|---|---|
| async-whitepaper | Источник дизайна, теоретическая база |
| language-spec | Целевая спецификация |
Аннотация
Предлагается модель спавна для YaoXiang: синхронный синтаксис для описания логики с автоматическим конкурентным выполнением во время runtime. Ключевые механизмы: трёхуровневая архитектура конкурентности + DAG анализ зависимостей + система типов Result.
Быстрый выбор
| Сценарий | Способ записи | Описание |
|---|---|---|
| Автоматический параллелизм | Без аннотаций (по умолчанию) | Максимальный параллелизм |
| Синхронное ожидание | @eager | Ожидание завершения зависимостей |
| Полная последовательность | @block | Без конкурентности, для отладки |
| Локальная конкурентность | spawn | Конкурентность внутри области видимости @block |
Мотивация
Текущие модели конкурентности в мейнстрим языках имеют очевидные недостатки:
| Язык | Модель конкурентности | Проблема |
|---|---|---|
| Rust | async/await + tokio | Распространение асинхронности, крутая кривая обучения |
| Go | goroutine | Нет безопасности типов |
| Python | asyncio | Ограничения GIL |
| JavaScript | Promise/async | Сложные колбэки |
Ключевое противоречие
- Прозрачность vs Управляемость:Полная прозрачность, но нет контроля vs Полный контроль, но непрозрачность
- Конкурентность vs Отладка:Конкурентные программы сложно отлаживать vs Программы с отладкой сложно делать конкурентными
Предложение
1. Модель спавна: трёхуровневая архитектура конкурентности
Пояснение:L1/L2/L3 — это ментальная модель, помогающая пользователям понять различные сценарии. Реализация использует единый механизм: автоматический DAG-анализ + контроль через аннотации.
| Уровень | Ментальная модель | Синтаксис | Способ выполнения | Степень параллелизма |
|---|---|---|---|---|
| L1 | Запрет конкурентности | @block | Чисто последовательное выполнение | ❌ Нет |
| L2 | Конкурентность внутри @block | spawn | Управляемая конкурентность в области видимости @block | ⚠️ Частично |
| L3 | Полная конкурентность | По умолчанию (без аннотаций) | Автоматический анализ DAG | ✅ Полный |
L1: Синхронный режим @block
main: () -> Void @block = {
data1 = fetch_sync("api1")
data2 = fetch_sync("api2")
process(data1, data2) # Строгая последовательность, без конкурентности
}L2: Управляемая конкурентность внутри @block
# spawn можно использовать только внутри функций @block
main: () -> Void @block = {
spawn { data1 = fetch_data("api1") }
spawn { data2 = fetch_data("api2") }
# Ожидание завершения всех spawn (контроль стандартной библиотекой)
process(data1, data2)
}L3: Полная прозрачность (по умолчанию)
# Не нужны аннотации, компилятор автоматически анализирует DAG
heavy_calc: (n: Int) -> Int = fibonacci(n)
auto_parallel: (n: Int) -> Int = {
a = heavy_calc(1) # Автоматический параллелизм
b = heavy_calc(2) # Автоматический параллелизм
c = heavy_calc(3) # Автоматический параллелизм
a + b + c # При запросе значения ожидаем все результаты
}2. Полное сравнение аннотаций
| Измерение | По умолчанию (без аннотации) | @eager | @block | spawn |
|---|---|---|---|---|
| Способ выполнения | Автоматический DAG-анализ | Синхронное ожидание зависимостей | Чисто последовательно | Конкурентность внутри @block |
| Степень параллелизма | ✅ Полный | ⚠️ По порядку зависимостей | ❌ Нет | ⚠️ Частично |
| Построение DAG | ✅ | ✅ | ❌ | ✅ |
Руководство по выбору:
- Максимальная конкурентность → Без аннотаций (по умолчанию)
- Требуется упорядоченность побочных эффектов →
@eager - Отладка/новички/критический код →
@block - Нужна конкурентность внутри @block →
spawn
# По умолчанию: максимальный параллелизм
calc_all: () -> Int = {
a = heavy_calc(1) # Автоматический параллелизм
b = heavy_calc(2) # Автоматический параллелизм
a + b
}
# @eager: синхронное ожидание
calc_seq: () -> Int @eager = {
a = heavy_calc(1) # Синхронное выполнение
b = heavy_calc(2) # Синхронное выполнение
a + b
}
# @block: чисто последовательно
calc_simple: () -> Int @block = {
a = heavy_calc(1) # Принудительно синхронно
b = heavy_calc(2) # Синхронно
a + b
}
# spawn: конкурентность внутри @block
calc_mixed: () -> Int @block = {
spawn { heavy_calc(1) }
spawn { heavy_calc(2) }
heavy_calc(3) # Синхронно
}3. Анализ DAG зависимостей
3.1 Ключевой принцип: выполнение снизу вверх
Код пользователя (синхронный синтаксис):
a = fetch(url0)
b = fetch(url1)
print(a)
Анализ при компиляции (снизу вверх):
print(a) требует a → зависит от fetch(url0)
fetch(url1) никому не нужен → изолированный DAG
Планирование во время выполнения (от листьев):
fetch(url0) → print(a) ← цепочка зависимостей, по порядку
fetch(url1) ← изолированный, параллельно независимоКлючевой инсайт: 不是"自顶向下"生成 Future,而是"自底向上"从结果反向分析依赖。
3.2 Изолированный DAG: независимый параллелизм
Основной поток: fetch(url0) → process → print
Изолированный: fetch(url1) ← результат никому не нужен, параллельно независимо
Планировщик: основной поток выполняется по цепочке зависимостей, изолированный использует другое ядро параллельно3.3 Типы ресурсов и побочные эффекты
Ключевая идея: Операции с ресурсами помечаются через типы, DAG строится автоматически. Операции с одним ресурсом автоматически сериализуются, с разными ресурсами — автоматически параллелизуются.
Границы типов ресурсов——чёткое определение:
Resource types — это встроенные в компилятор типы-маркеры. Следующие типы распознаются компилятором как ресурсы:
| Тип ресурса | Описание | Поведение компилятора |
|---|---|---|
FilePath | Путь к файловой системе | Операции с одним путём автоматически сериализуются |
HttpUrl | HTTP endpoint | Операции с одним URL автоматически сериализуются |
DBUrl | Подключение к БД | Операции с одним подключением автоматически сериализуются |
Console | Стандартный вывод | Все операции Console автоматически сериализуются |
Пользовательские типы ресурсов требуют явного маркирования:
Database: Resource # Явное маркирование как тип ресурса
query: (Database, String) -> Result(Row, Error)
# Параметр Database — это Resource, автоматически распознаётся как операция с ресурсомТипы без маркера Resource не отслеживаются компилятором для зависимостей по ресурсам.
Правила использования:
- Передавайте дескрипторы ресурсов через переменные, DAG автоматически управляет порядком
- Использование литералов для одного ресурса — проблема дизайна пользователя, не ответственность языка
# ✅ Правильно: передача через переменную, DAG автоматически сериализует
filename: String = "data.txt"
File.write(filename, x)
File.write(filename, y) # DAG сериализует
# ⚠️ Ответственность пользователя: литералы
File.write("data.txt", x)
File.write("data.txt", y) # Может выполниться параллельно, пользователь сам отвечает3.4 Обработка бесконечных циклов
1 цикл → Непосредственно синхронное выполнение, нулевые накладные расходы на планирование
Несколько циклов → Планировщик разбивает на части и переключается, реальная конкурентность4. Result тип и обработка ошибок
Result: (T: Type, E: Type) -> Type = { ok: (T) -> Self, err: (E) -> Self }
# Оператор ? прозрачно распространяет
process: () -> Result(Data, Error) = {
data = fetch_data()?
processed = transform(data)?
save(processed)?
}5. Дизайн узлов DAG
enum NodeKind {
Task, // Узел задачи
Value, // Узел значения
Control, // Узел потока управления
}
struct Node {
id: NodeId,
kind: NodeKind,
inputs: Vec<ValueNodeId>, // Входные зависимости
outputs: Vec<ValueNodeId>, // Выходные значения
span: Span, // Позиция в исходном коде
}| Тип рёбер | Символ | Семантика |
|---|---|---|
| DataEdge | → | Зависимость по данным (поток значений) |
| ControlEdge | ● | Зависимость по управлению (последовательное выполнение) |
| SpawnEdge | ◎ | Точка входа конкурентности (возможная точка параллелизма) |
6. Система типов
Send → Можно безопасно передавать между потоками
Sync → Можно безопасно разделять между потоками
Arc(T) реализует Send + Sync (потокобезопасный счётчик ссылок)Компромиссы
Преимущества
- Постепенное принятие: Трёхуровневая модель адаптируется к разному уровню навыков
- Естественный синтаксис: Синхронный код получает производительность параллелизма
- Безопасность при компиляции: Ограничения Send/Sync устраняют гонки данных
- Отлаживаемость: Граф ошибок обеспечивает чёткое представление распространения ошибок
Недостатки
- Кривая обучения: Требуется понимание концепции DAG зависимостей
- Время компиляции: Полнопрограммный DAG-анализ может быть медленным
- Сложность инструментария: Требуется принципиально новый отладчик и инструменты визуализации
Альтернативные решения
| Решение | Почему не выбрано |
|---|---|
| Только явный async/await | Невозможно реализовать прозрачный параллелизм |
| Только полностью прозрачный параллелизм | Пользователь теряет контроль |
| Go-style goroutine | Нет безопасности типов, невозможна проверка при компиляции |
| Только режим L1 | Отказ от ключевой ценности модели спавна |
Стратегия реализации
Разделение на этапы
- Этап 1 (v0.1):Синхронный режим @block, базовые типы
- Этап 2 (v0.2):Планировщик FlowScheduler
- Этап 3 (v0.3):Блоки spawn, явная конкурентность
- Этап 4 (v0.5):Полная прозрачность L3, автоматический DAG-анализ
- Этап 5 (v0.6):Граф ошибок, отладчик графов
- Этап 6 (v1.0):Оптимизация для production
Зависимости
- RFC-001 без внешних зависимостей (базовое ядро)
- RFC-008(Конкурентная модель Runtime)→ Дизайн завершён
- RFC-011(Система generics)→ Дизайн завершён
Риски
- Производительность DAG-анализа: Полнопрограммный анализ может быть O(n²), требуется оптимизация
- Отсутствие инструментария: Отладчик требует разработки с нуля
- Принятие пользователями: Прозрачный параллелизм требует качественной документации
Журнал решений по дизайну
| Решение | Определение | Дата |
|---|---|---|
| Трёхуровневая архитектура конкурентности | L1/L2/L3 постепенно | 2025-01-05 |
| Позиция аннотации @block | После типа возврата | 2025-01-05 |
| Распространение ошибок DAG | Распространение вверх по рёбрам зависимостей | 2025-01-06 |
| Оптимизация производительности DAG | Инкрементное построение + кэширование | 2025-01-06 |
| Выбор runtime | Generics + внедрение при компиляции | 2025-01-06 |
| Интерфейс узлов | Generics + внедрение функций(без trait) | 2025-01-06 |
| Память графа ошибок | DAG строится только внутри одной функции | 2025-01-06 |
| Обнаружение конфликтов ресурсов | DAG-зависимость потока данных, передача пользовательских переменных | 2025-01-06 |
| Система типов ресурсов | Маркер Resource + автоматическая DAG-зависимость | 2026-01-06 |
| Ментальная модель L1/L2/L3 | Три уровня абстракции, не механизм реализации | 2026-01-06 |
| Аннотация @auto | Удалена, дублирует поведение по умолчанию | 2026-05-11 |
| Автоматический откат L1 | Удалён, поведение непредсказуемо | 2026-05-11 |
Приложение: Глоссарий
| Термин | Определение |
|---|---|
| Модель спавна | Парадигма конкурентности YaoXiang: синхронный синтаксис, асинхронная суть |
| DAG | Направленный ациклический граф, описывает зависимости вычислений |
| spawn | Управляемая конкурентность в области видимости @block |
| @block | Синхронная аннотация, отключает оптимизацию конкурентности |
| @eager | Спешное вычисление, ожидание завершения зависимостей |
| Resource | Маркер типа ресурса, операции автоматически строят DAG-зависимости |
| Граф ошибок | Визуализированный путь распространения ошибок |
