Недавно Россию посетила группа экспертов С++, участвующих в работе комитета по стандартизации. 25 февраля они участвовали в Q&A сессии, организованной Яндексом, а после выступали на конференции С++ Russia 2016. Одним из них был Гор Нишанов, автор предложения о включении C#-подобных сопрограмм (те которые async/await) в стандарт С++17. Ранее Гор выступал на CppCon 2015 с докладом "С++ Сопрограммы — Абстракция с отрицательным оверхедом". Такая возможность структурировать асинхронный код выглядит привлекательно, Гор в докладе убедительно показывает, что количество кода сокращается, а скорость работы возрастает по сравнению с «рукописными» State Machine. Кроме того, это одно из самых больших потенциальных изменений в следующем стандарте и привлекает к себе внимание. Судя по публикациям, proposal получил значительное одобрение и комитет склоняется к включению в стандарт.
После этого я был весьма удивлен, когда загуглив упомянутый proposal P0057, получил в выдаче встречный документ "Coroutines belong in a TS", который подвергает предлагаемую реализацию сопрограмм жесткой и весьма эмоциональной критике и требует не включать в С++17, а отложить для «обкатки» в Technical Specification. Отмечу, что не являюсь сторонником этих возражений, а приглашаю интересующихся к обсуждению, насколько обоснованны перечисленные претензии и все ли так плохо. Под катом «выжимка» из документа с небольшими комментариями.
Первое, что обращает внимание: одним из противников является Christopher Kohlhoff, автор и maintainer Boost.Asio, которого можно считать специалистом в асинхронном программировании. Затем бросается в глаза резкий и эмоциональный тон документа. Не просто мелкие замечания, а принципиальное несогласие. Приведу несколько цитат:
Предложение кажется копирует из других языков, но не учится на ошибках, которые они сделали. Мы не должны слепо копировать эти ошибки в С++.
… стремление включить в С++17 такое предложение и поспешное и глупое.
Учитывая, что мы имеем шанс стать одним из первых языков, в котором это реализовано правильно, я удивлен, что мы так спешим стандартизовать неоптимальный, я бы даже сказал, сломанный подход.
Есть два больших красных флага, которые люди игнорируют в стремлении добавить «большой билет» и «крутую» фичу в С++17.
Итак, какие же претензии предъявляются:
1) Самый главный пункт: необходимость использования ключевого слова await, чтобы помечать асинхронные вызовы, на которых происходит suspend. Эту модель авторы документа называют «suspend-up» и перечисляют такие недостатки:
- Модель является инвазивной, «вирусно» распространяется по кодовой базе, по всему стеку асинхронных вызовов.
- Требуется значительное изменение уже существующего кода, чтобы он использовал сопрограммы.
- Если код из 3rdparty библиотеки вызывает наш асинхронный callback, то может потребоваться добавить await и в него.
- Нельзя использовать стандартные алгоритмы STL с асинхронным кодом, нужно писать специальные «асинхронные версии». Авторы опасаются, что это может привести к «копии STL» и дублированию кода.
- Требует добавления нового ключевого слова и не может быть реализовано только на уровне библиотеки, как, например, Boost.Coroutines.
Этой модели авторы противопоставляют так называемую «suspend-down» модель, которая не является инвазивной и используется в Boost.Coroutines. Она также предлагалась в комитет, «Resumable Expression» от Christopher Kohlhoff, но предпочтение было отдано С#-подобному await.
Отличие этой модели в том, что не требуется помечать асинхронные вызовы словом await. Вызывается сущность, которая «снаружи выглядит как обычная функция», например, suspend(). Она внутри осуществляет переключение контекста (Boost.Coroutines использует Boost.Context) без выхода из вызывающего метода и «подвешивает» весь стек вызовов до завершения асинхронной операции. При этом первые функции в цепочке могут даже «не знать», что вызывают асинхронную операцию, и не требуется модификация существующего кода.
На эту тему Гор Нишанов отвечал следующее (видео доклада на CppCon, Q&A в Яндексе):
- Да, модель с await инвазивная и требует модификации кода. Как и использование std::future и std::promise. Если вы готовы применять std::future, то сопрограммы с await предлагают вам более простой и понятный путь.
- Да, модель Boost.Coroutines (он назвал ее «Fibers») имеет свои сильные стороны и свою область применения. Это не взаимоисключающие модели и обе будут включены в С++ рано или поздно. И могут даже применяться совместно для различных задач.
2) Следующее на что указывают авторы документа: при проектировании сопрограмм была проигнорирована значительная часть опыта и use-case'ов для платформ, которые требуют малого времени отклика. Для примера приводятся системы для финансовых рынков. Указывается, что работа таких систем регламентируется законами, а отказ может повлечь расследование и судебное преследование.
По этой причине мысленный эксперимент не подходит и сопрограммы должны быть предварительно испытаны в работе в таких областях.
3) Далее идет интересный аргумент в стиле «столкновение миров»: Linux — это наиболее распространенная платформа для высокопроизводительных систем, поэтому пробная реализация сопрограмм для MSVC/Windows мало что дает для оценки производительности.
4) Сопрограммы в стиле C# await — во многом результат простого копирования синтаксиса из других языков. Скопировали и запустили в работу. При этом были скопированы и ошибки и недостатки сопрограмм из этих языков (C#, Python).
Кроме того, модель сопрограмм во многом была позаимствована из динамических языков. И очень вероятно, что она является не оптимальной и для С++ можно сделать лучше.
5) Риск для безопасности и производительности: текущий дизайн сопрограмм увеличивает риск jitter'a, недостатка ресурсов отдельным задачам (starvation) и DOS атаки. Одной из причин является то, что сопрограммы используют кооперативную многозадачность, а не вытесняющую. Поэтому при малом числе потоков одна сопрограмма может захватить ресурсы надолго и планировщик ОС не придет на помощь.
Честно говоря, мне этот пункт показался несколько притянутым. Во всяком случае отдельные детали реализации могут быть исправлены и принципиальных недостатков, присущих модели, не видно.
6) Риск для корректности: после завершения асинхронной операции сопрограмма может быть продолжена на другом потоке. Авторы считают, что такое поведение допустимо, но оно не должно быть по умолчанию.
В то же время в C# await эта проблема прекрасно решается. Если операция запускается из UI потока, то продолжение (continuation) всегда вызывается тоже в UI потоке.
7) Риск, что если модель await (suspend-up) будет принята, то это осложнит включение в стандарт suspend-down модели (по образцу Boost.Corountines). Станет трудно убеждать тратить время комитета по стандартизации на функциональность, которая «уже есть».
Если бы предложенные await-сопрограммы реализовывались только на уровне библиотеки, то это оставляло бы пространство для маневра и исследования альтернативных вариантов. Но в текущей форме может не быть пути назад.
Предложения
1) Отложить включение сопрограмм в С++17 и включить пока в Technical Specification для «обкатки».
2) Продолжать работу над suspend-down моделью и возможно сформировать два предложения по сопрограммам в будущие стандарты «Suspend-Up Coroutines» и «Suspend-Down Coroutines».
3) Текущее предложение по сопрограммам в значительной степени получило преимущество потому, что Гор Нишанов имел возможность экспериментировать и дорабатывать компилятор Visual Studio в Microsoft. Поэтому авторы альтернативной модели сопрограмм ожидают сотрудничества с разработчиками GCC и Clang для доработки и экспериментов на высоконагруженных Linux системах.
Заключение
В заключение авторы упоминают, что есть два «больших красных флага»:
1) Те участники комитета, которые имеют большой опыт применения сопрограмм в высоконагруженных системах, по разным причинам имеют мало влияния.
2) В настоящее время есть несколько предложений в Technical Specification: Parallelism, Concurrency, Networking и потенциально «Suspend-Up Coroutines» и «Suspend-Down Coroutines», которые требуют единой и последовательной модели или по крайней мере продуманного взаимодействия.
Поэтому нужно время и торопиться с сопрограммами не стоит.
А каково ваше мнение? Насколько критичны перечисленные недостатки?
И стоит ли откладывать сопрограммы, чтобы позднее включить в стандарт сразу две разновидности?
Автор: axden