Подождал да…
Зависит от процессов в которых вы работаете или на какие хотите выйти.
В лине (lean) «подождал» это уже waste, пусто потраченное время.
Возможно для процессов поддержки продукта, багфикса, ревью более-менее рабочее решение как девелоперов хоть как-то мотивировать читать чужой код и делится опытом и наработками.
Но когда команда уже работает над одним и тем же модулем (а не в отдельных огородиках), в тесном контакте, работает инкрементально, ревью становятся 3ей ногой которая мало того что требует ресурсов и времени на поддержку так еще и может захоти пойти в другую сторону. Получается такое ревью ради ревью, которое люди по старой памяти пытаются притянуть куда можно.
При работе удаленно парное последовательное программирование на моем опыте работает просто огненно: один девелопер заканчивает работу над модулем в своем часовом поясе/графике, отписывает что сделал и что получилось что еще нет, второй подхватывает доделывает/исправляет, и передает работу опять первому. Очень эффективно работает.
В том же ревью по умолчанию предполагается что если написали коментарий, то автор должен внести изменения. В случае с удаленной работой/поясами задержка минимум день или два, плюс добавим пару дней если кто-то чего-то не понял. К чему этот цирк и почему коментатору взять и не исправить самому? Опять же на моей практике это еще более эффективно чем писать простыни текста в ревью, взял и исправил.
Далее работа инкрементально… Представьте идет работа над новым продуктом, в идеологии агиле/лин: нет железного дизайна, есть цели которые достигают инкрементально, дизайн строится по ходу понимания проблемы все лутше и лутше, на каждой итерации рефакторя или переделывая первоначальные концепты. И вот, на первой или второй итерации вся команда начинает ревьювить и вылизывать первоначальный пруф-концепт, который наверняка в 4ой или 5ой итерации полностьт перепишут. Какой смысл это ревьювить и вылизывать? Никакого.
Надо сказать что я больше пишу про ревью внутри команды. Команда это 2-5 человек, работающая тесно над одной проблемой, в практически полной свободе. Бывает если надо внести изменения в какую-то изолированную организацию/группу ревью может и понадобится (или пулл реквест, смотря что они используют), но обычно это костыли и команда уже не кросс-функциональная.
Проблема формальных ревью имхо в их крайней неэффективности.
1) по сути они склоняют всех к мультитаскингу — ибо сделал изменения — жди вопросов/предложений, а чтобы время не пропадало займись чем то еще, и так переключайся туда-сюда по кругу. Мультитаскинг надо избегать ибо он жрет эффективность и концентрацию.
2) зажимают все общение в рамки письменного изложения. Письменно прийти к решению/пониманию в разы сложнее/дольше чем вербально. Не все умеют излагать мысли на бумаге, не все умеют понимать абстрактные идеи изложенные письменно (кому то нужна картинка например или какая то жизненная аналогия).
3) люди в большинстве своем не умеют передавать эмоции письменно (не все родились поэтами), соответсвенно могут портится отношения на ровном месте (фокус статьи я так понимаю на этом), ибо нет эмоционального фидбэка и корректировки.
Так что если можете избегать формального ревью — делайте это.
Парное программирование, работа всей команды над одним модулем/компонентом (а не каждый в своем огородике), правило "максимально просто плюс есть тест — ревью пройден заочно", кросфункциональные команды (таск форс), и многое другое позволяет достигнуть всех плюсов формальных ревью не сжигая на это времени/нервов/ресурсов.
Автор просто тренируется и в коде много велосипедов которые можно убрать, там 80% занимает имплементация того что уже есть в std (std::function). А лямбда просто позволяет писать короче и ничего больше, например раньше было:
struct MyFunc
{
… // ручками сохраняем все окружение и прокидываем в конструктор
void operator()
{
…
}
};
another_func( MyFunc() );
то сейчас можно
another_func( ()[] {… } ); // окружение можно перечислить в [] и их прокинет компилятор за нас
грубо стало меньше печатанья для создания калбеков.
Плюс затащили std::function из буста чтобы можно было такие калбеки удобно сохранять и передавать.
Эти вещи и раньше на C++ делали, просто печатать приходилось гораздо больше, а теперь компилятор в этом деле помогает.
особой связи с мульти-тредингом я как-то в коде не вижу — наверно около 80% кода имплементят std::function.
оставшееся thread pool (который называется почему-то control).
думаю чтобы было ОК для «изучаем C++11» надо сделать следющее:
1) все что связано с «описаниями» тасков перевести на std::function, лямбды никуда от этого не пропадут и опыт их использования. написание своего std::function — это целая тема и не связана с асинхронностью.
2) выпилить std::cond и std::mutex для каждой задачи, список тасков может быть просто списком std::function. создавать тысячи или миллионы мутексов — как минимум странно, а в реальной жизни уж очень накладно.
3) попробывать упростить класс control, в нем «много пены»
4) упростить main — зачем ждать окончания каждого таска индивидуально? ждите пока тред пул не станет пустым или расширте интерфейс и дайте таскам приоритет/порядок исполнения. иначе получается что логика тред пула перекочевала в main.
в результате код уменьшится раз так в 5 и C++11 усвоится лутше. а тему «как самому сделать std::function» лутше изучать отдельно, там выше крыше своих хитростей.
Энум это сериализация состояния, а далее чтобы это состояние «достать» — его надо диспатчить через if() или switch(). Т.е. это вид полиморфизма такой исторический.
Особенно хорошо это видно на функциях OnRead и OnWrote — switch() во всем своем великолепии. Совершенно бесполезная сериализация состояния имхо и заодно попытка заимплементить свой велосипедик по диспатчу. А что мешает использовать теже лямбды по прямому назначению (а не как «украшательства»)? В бусте когда постится асинхронный read/write можно спокойно передать лямбду напрямую из нужного состояния и забыть про енумы и switch() из мира C. Но так как в сабжевой либе это не учтено, все проходит через бутылочное горлышко OnRead, и собственно начинаем воевать сами с собой.
Имхо обилие энумов в C++ коде — признак того что ООП автором не понят — пишем на C используя C++.
И код на boost::asio хттп сервера будет пожалуй даже короче. :)
Относительно простое решение есть через множество методов c return *this:
some_object.width( 10 ).height( 15 ).hidden( true );
А функции с более чем 2-3 параметрами вообще не стоит создавать имхо, тогда и боротся с ними не придется.
tup не быстрее ninja но при этом сильно ограничен в том что может делать. tup не позволяет (покрайней мере раньше не позволял и автор не собирался это исправлять хоть я и предлагал модель как это можно сделать) запускать команды которые правят несколько файлов в разных папках, как по мне абсолютно фатальное ограничение для данной билд тулзы.
Одно время я пытался использовать tup — даже засабмитил 3 или 4 фикса чтобы он нормально заработал под win32 но:
а) выше описанное ограничение по выходам команд (нельзя запустить патч сорцов во время билда, или генерацию сложных ресурсов)
б) win32 порт сильно отстает от *nix (на win32 даже нет поддержки out of source build)
в) сложная имплеметнация как на *nix так и на win32 которая часто глючит — tup через хуки мониторит процессы запущенные их под него чтобы обнаружить все зависимости и входы-выходы
г) сильно ограниченные возможности скриптов, язык tup даже не прячет особенности win32 vs *nix
в результате после прототипа на tup и обраружения всех этих недостатков перевел проекты над которыми работал на cmake + ninja.
make не особо кстати и минималистичен, в нем просто сотнями встроенных правил срабатывающих по умолчанию. причем даже с этими встроенными правилами пользоватся makeом достаточно муторно: сделать простейшую вещь — отделить бинарники от исходников (out of source tree build) еще то занятие.
Если и смотреть на минималистичные системы сборки — то пожалуй это ninja (http://martine.github.io/ninja/), мало того что он проще так он еще и работает ощутимо быстрее (на инкрементальных сборках более-менее больших проектов).
Причем в последних CMake есть возможность использовать ninja для генерации собственно билд скриптов и получается девелопер может получить все лутшее от обоих миров — высокий уровень CMake плюс скорость сборки ninja. Чем сейчас и пользуюсь. )
Имхо есть разные типы усложнений, врятли их можно все в одну кучу пихать. Скорее оба типа людей обозначенные в статье усложняют по своему.
Например код КОП может быть технически простым (длинная простыня без попыток выцепить повторяющиеся фрагменты, понять где еще в программе такое же происходит и так далее — такой код легко читать не вникая в контекст всего приложения), но изза того что над ним мало думали (зачем думать над простыми вещами?) он будет логически сложен (запутанные условия, много лишних состояний, условия которые не имеют смысла) в результате делая такой код сложнее для обобщения и соответственно поддержки.
В свою очередь код АОП может быть логическим простым (состояний ровно столько сколько нужно, вылизанные условия, алгоритм реализован кристально чисто) но технически сложным (используются технические навороты аля дабл-диспатч, декомпозиция на многие классы) что для людей которые не хотят вникать в контекст проблемы будет выглядить чрезмерно сложным в плане начального понимания.
В какой сложности истина я не знаю, но рутину не люблю, поэтому предпочитаю АОП ).
Автор написал что «SJLJ Относится скорее к первому подходу» что меня смутило, невнимательно читал (скорее). SJLJ наверно относится к подходу «хотели как лутше а получилось как всегда» ).
Незнаю как на x86/x64 но на Arm GCC/SJLJ дает жуткий оверхед по размеру кода (30-50%) даже если исключения вообще не кидать (т.е. достаточно просто скомпилировать С++ код с поддержкой исключений). Никак не могу назвать такой подход «пусть неудачник платит», там платят все и всегда, причем так прилично что на дремучих embedded их тупо отключают.
В этом плане GCC/DW2 более полезен так как не генерит такого оверхеда (по % сейчас не скажу, но занчительно меньше) — как написано для раскрутки стека там используются сжатые таблицы плюс динамической дизасемблирование/анализирование самих фреймов для получения недостающей информации. И то что данные раскрутки находятся отдельно от полезного кода — неоспоримый плюс — кеш процессора меньше захламляется тем что в теории должно редко использоватся. То что сама раскрутка становится медленней имхо проблемой не является, не в этом смысл исключений крутить их в цикле и ожидать реактивной производительности.
Кстати говоря раскрутка стека в коде нужна не только для C++ а например для красивых краш-дампов или анализа утечек памяти. В этом плане GCC/DW2 полезней GCC/SJLJ хотя бы тем что может использоватся в C для улутшений диагностики кода. Тот-же valgrind помоему раскручивает стек на ARMе через GCC/DW2 и если его нет с раскруткой стека начинются серьезные проблемы (на ARMе).
>> Если у Вас большой и сложный проект, то скорее всего ни одна система непрерывной интеграции из коробки не даст Вам то, чего вы хотите.
Может конечно включаю режим капитана очевидность но по хорошему большой и сложный проект должен собиратся «одним нажатием клавиши» в полном цикле (апдейт версий, генерация документации, запуск тестов, создание тагов и так далее) и без билд машины (и соотвественно без системы непрервыной интеграции которая выше уровнем). В таком случае системы непрерывной интеграции по сути становятся job schedulerами с разными удобностями:
1) триггер билда/job по коммиту, мониторинг исходного кода
2) сохранение артефактов в удобные места (tm)
3) красивые отчеты в вебе и сбор всякой ненужной но красивой статистики
4) визуализация результатов юнит тестов
5) массовый россыл мейлов при сломе билда или тестов (а иногда и просто так)
6) интеграция с багтрекингом (отметить какие тикеты попали в какой билд и так далее)
7) и тому подобное
И вся магия таких систем обычно состоит в том как сложно эти штуки настроить. Главное не пытатся из нее выдавить то что она не должна делать (заниматся сборкой отдельных компонентов, лезть в зависимости между внутренними модулями проекта, прыгать на уровень cmake/ninja/autotools etc).
Есть еще вариант как сделать домашний роутер + сервер -> использовать гипервизор и виртуализацию.
В новейших гипервизорах устройства PCI-express можно целиков передавать в виртуальные машины, т.е. виртуальная машина может иметь полный контроль над сетевыми интерфейсами.
Инсталим гипервизор, под него виртуалку которой отдаем все сетевые карты, там уже настраиваем роутер как описано.
Далее чтобы железо не простаивало отдельными виртуалками можно добавлять сервера, так безопасней чем например на роутер ставить сервера.
У меня на домашнем компе стоят несколько Linux, роутер (обслуживает доманшюю LAN, отдельный LAN для серверов, два внешних IP через которые роутится трафик в зависимости от внутреннего LAN), плюс игровая Windows 8 (c прокинутой графикой). Под Xen все.
Бонус такого подхода в том что упрощается бекап и восстановление, можно делать откаты и мирроры всех виртуалок, плюс железо занимается не только одной задачей (домашний комп обычно достаточно мощный).
А какой инструментарий/интерфейс будет у сопрограммы на таймауты, напрямую дергать io_service таймеры или что-то свое? Т.е. надо же как-то отмечать — тут таймер начался, тут закончился, тут вообще отключаем его, тут несколько таймеров считаются паралельно и так далее.
Например нам надо посчитать keep-alive timeout из HTTP 1.1, это когда вообще ничего не приходит ~5 секунд.
Ну или грубо если клиент что-то начал слать, то максимум собирать пакет 15 секунд, если не собрался — отключаем.
Просто на моем опыте в линейном коде — блокирующемся или на сопрограммах — таймеры считать напряжно, пропадает весь смысл линейности так как таймеры по сути своей асинхронны — они сработать грубо могут в любом месте где происходит блокировка/resume.
А если делать гибридный подход (io_service-таймеры но остальной код на сопрограммах) то как-бе пропадают плюсы сопрограмм — человеку придется погружатся в пробематику обоих подходов.
>> Теорема. Любую асинхронную задачу можно решить с помощью сопрограмм.
Есть одна важная проблема которую решать с помощью сопрограмм сложно и не удобно а на асинхронном подходе «бесплатно» — обработка таймаутов.
В перечисленных примерах HTTP сервера обработка таймаутов скромно пропущена. Но это чуть ли не главная проблема для реальных серверов — быстро уметь разруливать кривых клиентов или проблемы со связью. 30К запросов в секунду-минуту это конечно хорошо в идеальных условиях, а что если 30к подключений на вашем сервере начнут играть «в дурачка» и будут слать по 1му байту в минуту. Для остальных пользователей сервер скорей всего ляжет, кол-во доступных портов же не резиновое.
Стандартные задачи при имплементации протокола — посчитать таймаут между подключением и первым запросом пользователя. Не первым полученным байтом а именно полным запросом. Или заимплементить ограничение на кол-во запросов в минуту сильно нагружающих сервер. На boost::asio это просто шедулинг таймера одной строкой и обработка еще одного калбека, а как в сопрограммах то это сделать?
Зависит от процессов в которых вы работаете или на какие хотите выйти.
В лине (lean) «подождал» это уже waste, пусто потраченное время.
Возможно для процессов поддержки продукта, багфикса, ревью более-менее рабочее решение как девелоперов хоть как-то мотивировать читать чужой код и делится опытом и наработками.
Но когда команда уже работает над одним и тем же модулем (а не в отдельных огородиках), в тесном контакте, работает инкрементально, ревью становятся 3ей ногой которая мало того что требует ресурсов и времени на поддержку так еще и может захоти пойти в другую сторону. Получается такое ревью ради ревью, которое люди по старой памяти пытаются притянуть куда можно.
При работе удаленно парное последовательное программирование на моем опыте работает просто огненно: один девелопер заканчивает работу над модулем в своем часовом поясе/графике, отписывает что сделал и что получилось что еще нет, второй подхватывает доделывает/исправляет, и передает работу опять первому. Очень эффективно работает.
В том же ревью по умолчанию предполагается что если написали коментарий, то автор должен внести изменения. В случае с удаленной работой/поясами задержка минимум день или два, плюс добавим пару дней если кто-то чего-то не понял. К чему этот цирк и почему коментатору взять и не исправить самому? Опять же на моей практике это еще более эффективно чем писать простыни текста в ревью, взял и исправил.
Далее работа инкрементально… Представьте идет работа над новым продуктом, в идеологии агиле/лин: нет железного дизайна, есть цели которые достигают инкрементально, дизайн строится по ходу понимания проблемы все лутше и лутше, на каждой итерации рефакторя или переделывая первоначальные концепты. И вот, на первой или второй итерации вся команда начинает ревьювить и вылизывать первоначальный пруф-концепт, который наверняка в 4ой или 5ой итерации полностьт перепишут. Какой смысл это ревьювить и вылизывать? Никакого.
Надо сказать что я больше пишу про ревью внутри команды. Команда это 2-5 человек, работающая тесно над одной проблемой, в практически полной свободе. Бывает если надо внести изменения в какую-то изолированную организацию/группу ревью может и понадобится (или пулл реквест, смотря что они используют), но обычно это костыли и команда уже не кросс-функциональная.
Проблема формальных ревью имхо в их крайней неэффективности.
1) по сути они склоняют всех к мультитаскингу — ибо сделал изменения — жди вопросов/предложений, а чтобы время не пропадало займись чем то еще, и так переключайся туда-сюда по кругу. Мультитаскинг надо избегать ибо он жрет эффективность и концентрацию.
2) зажимают все общение в рамки письменного изложения. Письменно прийти к решению/пониманию в разы сложнее/дольше чем вербально. Не все умеют излагать мысли на бумаге, не все умеют понимать абстрактные идеи изложенные письменно (кому то нужна картинка например или какая то жизненная аналогия).
3) люди в большинстве своем не умеют передавать эмоции письменно (не все родились поэтами), соответсвенно могут портится отношения на ровном месте (фокус статьи я так понимаю на этом), ибо нет эмоционального фидбэка и корректировки.
Так что если можете избегать формального ревью — делайте это.
Парное программирование, работа всей команды над одним модулем/компонентом (а не каждый в своем огородике), правило "максимально просто плюс есть тест — ревью пройден заочно", кросфункциональные команды (таск форс), и многое другое позволяет достигнуть всех плюсов формальных ревью не сжигая на это времени/нервов/ресурсов.
struct MyFunc
{
… // ручками сохраняем все окружение и прокидываем в конструктор
void operator()
{
…
}
};
another_func( MyFunc() );
то сейчас можно
another_func( ()[] {… } ); // окружение можно перечислить в [] и их прокинет компилятор за нас
грубо стало меньше печатанья для создания калбеков.
Плюс затащили std::function из буста чтобы можно было такие калбеки удобно сохранять и передавать.
Эти вещи и раньше на C++ делали, просто печатать приходилось гораздо больше, а теперь компилятор в этом деле помогает.
оставшееся thread pool (который называется почему-то control).
думаю чтобы было ОК для «изучаем C++11» надо сделать следющее:
1) все что связано с «описаниями» тасков перевести на std::function, лямбды никуда от этого не пропадут и опыт их использования. написание своего std::function — это целая тема и не связана с асинхронностью.
2) выпилить std::cond и std::mutex для каждой задачи, список тасков может быть просто списком std::function. создавать тысячи или миллионы мутексов — как минимум странно, а в реальной жизни уж очень накладно.
3) попробывать упростить класс control, в нем «много пены»
4) упростить main — зачем ждать окончания каждого таска индивидуально? ждите пока тред пул не станет пустым или расширте интерфейс и дайте таскам приоритет/порядок исполнения. иначе получается что логика тред пула перекочевала в main.
в результате код уменьшится раз так в 5 и C++11 усвоится лутше. а тему «как самому сделать std::function» лутше изучать отдельно, там выше крыше своих хитростей.
Особенно хорошо это видно на функциях OnRead и OnWrote — switch() во всем своем великолепии. Совершенно бесполезная сериализация состояния имхо и заодно попытка заимплементить свой велосипедик по диспатчу. А что мешает использовать теже лямбды по прямому назначению (а не как «украшательства»)? В бусте когда постится асинхронный read/write можно спокойно передать лямбду напрямую из нужного состояния и забыть про енумы и switch() из мира C. Но так как в сабжевой либе это не учтено, все проходит через бутылочное горлышко OnRead, и собственно начинаем воевать сами с собой.
И код на boost::asio хттп сервера будет пожалуй даже короче. :)
some_object.width( 10 ).height( 15 ).hidden( true );
А функции с более чем 2-3 параметрами вообще не стоит создавать имхо, тогда и боротся с ними не придется.
Одно время я пытался использовать tup — даже засабмитил 3 или 4 фикса чтобы он нормально заработал под win32 но:
а) выше описанное ограничение по выходам команд (нельзя запустить патч сорцов во время билда, или генерацию сложных ресурсов)
б) win32 порт сильно отстает от *nix (на win32 даже нет поддержки out of source build)
в) сложная имплеметнация как на *nix так и на win32 которая часто глючит — tup через хуки мониторит процессы запущенные их под него чтобы обнаружить все зависимости и входы-выходы
г) сильно ограниченные возможности скриптов, язык tup даже не прячет особенности win32 vs *nix
в результате после прототипа на tup и обраружения всех этих недостатков перевел проекты над которыми работал на cmake + ninja.
Если и смотреть на минималистичные системы сборки — то пожалуй это ninja (http://martine.github.io/ninja/), мало того что он проще так он еще и работает ощутимо быстрее (на инкрементальных сборках более-менее больших проектов).
Причем в последних CMake есть возможность использовать ninja для генерации собственно билд скриптов и получается девелопер может получить все лутшее от обоих миров — высокий уровень CMake плюс скорость сборки ninja. Чем сейчас и пользуюсь. )
Например код КОП может быть технически простым (длинная простыня без попыток выцепить повторяющиеся фрагменты, понять где еще в программе такое же происходит и так далее — такой код легко читать не вникая в контекст всего приложения), но изза того что над ним мало думали (зачем думать над простыми вещами?) он будет логически сложен (запутанные условия, много лишних состояний, условия которые не имеют смысла) в результате делая такой код сложнее для обобщения и соответственно поддержки.
В свою очередь код АОП может быть логическим простым (состояний ровно столько сколько нужно, вылизанные условия, алгоритм реализован кристально чисто) но технически сложным (используются технические навороты аля дабл-диспатч, декомпозиция на многие классы) что для людей которые не хотят вникать в контекст проблемы будет выглядить чрезмерно сложным в плане начального понимания.
В какой сложности истина я не знаю, но рутину не люблю, поэтому предпочитаю АОП ).
В этом плане GCC/DW2 более полезен так как не генерит такого оверхеда (по % сейчас не скажу, но занчительно меньше) — как написано для раскрутки стека там используются сжатые таблицы плюс динамической дизасемблирование/анализирование самих фреймов для получения недостающей информации. И то что данные раскрутки находятся отдельно от полезного кода — неоспоримый плюс — кеш процессора меньше захламляется тем что в теории должно редко использоватся. То что сама раскрутка становится медленней имхо проблемой не является, не в этом смысл исключений крутить их в цикле и ожидать реактивной производительности.
Кстати говоря раскрутка стека в коде нужна не только для C++ а например для красивых краш-дампов или анализа утечек памяти. В этом плане GCC/DW2 полезней GCC/SJLJ хотя бы тем что может использоватся в C для улутшений диагностики кода. Тот-же valgrind помоему раскручивает стек на ARMе через GCC/DW2 и если его нет с раскруткой стека начинются серьезные проблемы (на ARMе).
Может конечно включаю режим капитана очевидность но по хорошему большой и сложный проект должен собиратся «одним нажатием клавиши» в полном цикле (апдейт версий, генерация документации, запуск тестов, создание тагов и так далее) и без билд машины (и соотвественно без системы непрервыной интеграции которая выше уровнем). В таком случае системы непрерывной интеграции по сути становятся job schedulerами с разными удобностями:
1) триггер билда/job по коммиту, мониторинг исходного кода
2) сохранение артефактов в удобные места (tm)
3) красивые отчеты в вебе и сбор всякой ненужной но красивой статистики
4) визуализация результатов юнит тестов
5) массовый россыл мейлов при сломе билда или тестов (а иногда и просто так)
6) интеграция с багтрекингом (отметить какие тикеты попали в какой билд и так далее)
7) и тому подобное
И вся магия таких систем обычно состоит в том как сложно эти штуки настроить. Главное не пытатся из нее выдавить то что она не должна делать (заниматся сборкой отдельных компонентов, лезть в зависимости между внутренними модулями проекта, прыгать на уровень cmake/ninja/autotools etc).
В новейших гипервизорах устройства PCI-express можно целиков передавать в виртуальные машины, т.е. виртуальная машина может иметь полный контроль над сетевыми интерфейсами.
Инсталим гипервизор, под него виртуалку которой отдаем все сетевые карты, там уже настраиваем роутер как описано.
Далее чтобы железо не простаивало отдельными виртуалками можно добавлять сервера, так безопасней чем например на роутер ставить сервера.
У меня на домашнем компе стоят несколько Linux, роутер (обслуживает доманшюю LAN, отдельный LAN для серверов, два внешних IP через которые роутится трафик в зависимости от внутреннего LAN), плюс игровая Windows 8 (c прокинутой графикой). Под Xen все.
Бонус такого подхода в том что упрощается бекап и восстановление, можно делать откаты и мирроры всех виртуалок, плюс железо занимается не только одной задачей (домашний комп обычно достаточно мощный).
Например нам надо посчитать keep-alive timeout из HTTP 1.1, это когда вообще ничего не приходит ~5 секунд.
Ну или грубо если клиент что-то начал слать, то максимум собирать пакет 15 секунд, если не собрался — отключаем.
Просто на моем опыте в линейном коде — блокирующемся или на сопрограммах — таймеры считать напряжно, пропадает весь смысл линейности так как таймеры по сути своей асинхронны — они сработать грубо могут в любом месте где происходит блокировка/resume.
А если делать гибридный подход (io_service-таймеры но остальной код на сопрограммах) то как-бе пропадают плюсы сопрограмм — человеку придется погружатся в пробематику обоих подходов.
Есть одна важная проблема которую решать с помощью сопрограмм сложно и не удобно а на асинхронном подходе «бесплатно» — обработка таймаутов.
В перечисленных примерах HTTP сервера обработка таймаутов скромно пропущена. Но это чуть ли не главная проблема для реальных серверов — быстро уметь разруливать кривых клиентов или проблемы со связью. 30К запросов в секунду-минуту это конечно хорошо в идеальных условиях, а что если 30к подключений на вашем сервере начнут играть «в дурачка» и будут слать по 1му байту в минуту. Для остальных пользователей сервер скорей всего ляжет, кол-во доступных портов же не резиновое.
Стандартные задачи при имплементации протокола — посчитать таймаут между подключением и первым запросом пользователя. Не первым полученным байтом а именно полным запросом. Или заимплементить ограничение на кол-во запросов в минуту сильно нагружающих сервер. На boost::asio это просто шедулинг таймера одной строкой и обработка еще одного калбека, а как в сопрограммах то это сделать?