Обновить
129
0
Григорий Демченко@gridem

Software Engineer

Отправить сообщение
Было бы интересно увидеть как выглядит график стеков собранный через perf record для приложения использующего такую модель.

Вполне нормально выглядит. Так же, как и обычные потоки.


Какое количество от общего времени работы будут занимать используемые примитивы синхронизации?

Зависит от реализации. Текущая реализация не самая эффективная по ряду причин. Но известно, как это поправить, чтобы стало сильно лучше. Тогда и бенчмарки можно будет провести.


Так же интересно как отслеживать такие вещи как длины очередей.

Через метрики, статистики и логи. Всё как всегда.


Когда мы хотим выполнять много задач на каком-то конкретном потоке или пулле потоков. Те делаем много телепортов в 1 поток?

Не понял вопроса.

И тут либо ты находишь возможность разъяснить людям свой подход, либо миришься с тем, что твой подход не понимают и не используют.

Всему свое время.

Сопрограммы показываются как обычные потоки с нормальным стек трейсом. Единственный момент: отладчик может показать только те сопрограммы, которые активны, т.е. сидят на потоках. Замороженные сопрограммы не видны, если не предпринимать специальных действий.

Т.е. хотелось бы видеть не абстрактную репликацию в вакууме, а более конкретное описание того, что нужно было сделать, как было сделано, почему было сделано именно так.

Ты не поверишь, но мне тоже этого хочется. А пока есть то, что есть.


Если это кому-то полезно — вперед и с песней. Если непонятно, то можно перечитать предыдущие статьи и попробовать, например, решить задачу из второй статьи про асинхронность с использованием этого подхода.


Писать более подробно некоторые вещи, которые я уже описывал в предыдущих статьях, я не вижу особого смысла. Попытаться раскрыть на примерах — про это я себе пометочку сделал. На самом деле, в статье примеры есть, и они отражают некоторые элементы и паттерны использования.

Если у тебя что-то сложнее hello world, то в любом случае будет трудно дебажить. А дебажить гонки — это на любителя, поэтому лучше их вообще не допускать. Этот подход выпиливает целый класс трудноуловимых проблем.

Не, речь не про то. Вот когда человек видит что-то вроде foo.deferCall(bla-bla-bla), то он может предположить, что за этим скрывается какая-то непростая машинерия. А вот когда это выглядит как foo.call(bla-bla-bla), то догадаться о хитром поведении вызова не просто.

Это философский вопрос, что считать за основание. И в том и другом случае происходит вызов метода и передача управления, а как она происходит — это подробности реализации. Кому-то может понравиться эта явность и многословность, мне — нет. Если же необходимо сохранить семантику, например, не прерывать исполнение, то для этого существуют другие примитивы и об этом надо говорить явно. Иначе у нас абстракция начнет протекать.


Ну вот и надо бы рассказать про эту задачу, уверяю, далеко не все занимаются на C++ репликацией объектов по сети.

Ну вот здесь и рассказано: https://habrahabr.ru/post/267509/

Или использовать ctx::exec_ontop_arg

Так делать плохо, т.к. контекст должен быть правильный, и это бывает часто важно, ведь в деструкторах тоже могут быть действия.

Простите, а кто вам мешает это сделать?

Ну я имел в виду std::async, который возвращает std::future.


Реальная проблема std::future в другом: нет возможности зарегистрировать продолжение, из всех способов получения значения доступно только синхронное ожидание.

Все так.

А как решится проблема через лямбды? Сделать очередь лямбд на выполнение?

как программисту прикидывать, к каким накладным расходам приводит используемая им (или его коллегами) кухня? Например, написали некий класс Foo, обернули его в адаптер, потом накрутили вокруг него еще субъекторов. Как понять, насколько дорогим будет вызов Foo::bar?

Понять можно одним способом: профилированием кода. В этом контексте было бы неплохо сделать замеры на конкретных примерах, но это тоже потребует отдельной статьи.


насколько просто будет работать с кодом, в котором Foo::bar может приостанавливать текущую короутину, запихивать функтор в канал, из которого кто-то что-то вычитает на другой рабочей нити, начнет обработку, телепортируется куда-то и т.д.? При том, что изменение одного слова в определении субъектора для Foo может кардинально поменять способ выполнения Foo::bar. Т.е. речь о том, что C++ и так ругают за то, что код на C++ отличается неочевидностью, здесь же неочевидность становится главной движущей силой.

Тут надо смотреть на характеристику того или иного способа синхронизации. Если мы используем канал, то значит телепортация не позволит продолжит обработку и текущее исполнение будет ожидать телепортации обратно. В целом, исходя из опыта могу сказать, что лучше написать меньше кода, чем больше.


Понятно, что ответить на эти вопросы без какого-то опыта использования твоих субъекторов невозможно, нужно брать и пробовать. Но, к сожалению, из статей не складывается пока чего-то такого, чтобы вызвало желания взять и попробовать :(

Субъектор появился в результате решения практической задачи про реплицированный объект. Когда есть большое число состояний, которое необходимо синхронизировать и следить за контекстом исполнения — то тут подобные абстракции сильно упрощают код.


В реальности, конечно, надо еще больше совершенствовать этот подход. И двигаться в сторону транзакционности, чтобы думать о синхронизации еще меньше.

С каких пор гарантированная отмена таймера — грязный хак? Если деструктор таймера закончил работу — все, тайм-аут должен быть невозможен!

Речь шла про EventsGuard. Грязный хак — это фигура речи.


Предлагаю вам добавить изменить механизм событий так, чтобы событие было не объектом, а лямбдой. Это решит проблемы с приходящими невовремя событиями.

Не очень понимаю, о чем речь. Какая проблема решается? Можете привести кусок кода?

Ваша цель понятна. И ваши кубики в этой статье на мой взгляд будут понятны 1 из 10, или 10 из 100 сетевиков. Но если уж браться то охватывать все и сразу. А именно стать известным наставником в сетевом программировании и архитектуре. :) Раз уж вы в этом направлении двигаетесь. Таких кстати в рунете пока не видно.

Сейчас непонятно, в какую сторону я движусь. Я написал то, что хотел написать еще примерно 2 года назад. К текущему моменту накопилось множество интересных моментов, поэтому статья и получилась такой.


У вас в предыдущих докладах и статьях рассматривается пример прозрачного проксика. Т.е. создаем аксептор. Крутим цикл прочитал с сокета-записал в сокет. Дальше это все переводим в корутины с объяснениями итд.
К сожалению на практике это все не так просто.

Можно придумать множество разных примеров. Но для этого нужно много времени. Судя по всему, многие не понимают вообще, какая решается задача. Тут есть некоторая специфика, о которой нужно знать.


Вот смотрите. В самом простом приближении, любая архитектура сетевых приложений выглядит из трех кубиков. Первый «Принимаем в буфер». Второй «Обрабатываем буфер». Третий «Отправляем с буфера». Это серверные приложения. И клиентские почти те же три кубика. Только вместо отправки из буфера, данные забираются дальше до программной логики.

Это сильно упрощенное понимание процесса. Во второй статье, кстати, приведен пример решения реальной задачи. Стоит отталкиваться от нее.


В следующий раз больше подумаю про введение в проблематику на конкретных примерах.

ReadAdapter — это Promise или future.

Так promise или future?


А все остальные пассажи — это попытка создать Promise, который масштабируется по нескольким потокам (телепортация) с автоматической синхронизацией (субъектор).

Если внимательно прочитать, то окажется, что promise и future нигде не создаются. И что значит "масштабируется по нескольким потокам"? Не могли бы раскрыть этот интересный термин?

Можно как-то обозначить проблему которую решает автор?

Решается задача взаимодействия объектов в многопоточной среде.


Если говорить о практических вещах, то для комуникации сейчас есть такие популярные модели как boost::asio, node.js, ZeroMQ, и много других, каждая из них предлагает решение каких-то проблем и асинхронность.

boost::asio — это про асинхронность на callback. Чтобы понять, о чем статья, надо сначала пописать достаточно немало на этих самых колбеках, а затем подумать о том, как сделать проще и лучше. И вообще, boost::asio, node.js, ZeroMQ — не модели взаимодействий, а способ решения конкретных задач. Поэтому получается сравнение апельсинов с телевизорами. Чтобы это понять, достаточно ответить на вопрос: "зачем node.js если есть акторная модель?" Думаю, вы поймете всю абсурдность претензий.


Для паралельных вычислений опять же сложность не в примитивах синхронизации, а в эффективном разбитии задачи/алгоритма на потоки, и есть решения которые распаралеливают код автоматически (у Intel и что то в GCC).

Для параллельных вычислений есть свои замечательные фреймворки. Однако они начинают плохо работать в условиях асинхронного взаимодействия: UI, сеть, диск и др.


Если мы говорим о низко-уровневых примитивах синхронизации (обертках над примитивами OS) — то и Mutex/SpinLock уже тоже немного устарели в угоду condition variables…

Этот оксюморон может и имел место быть, если бы condition variables не использовали мьютексы в своих интерфейсах.


Опять же в C++ новом стандарте есть опять же свои обертки (std::future). Зачем выдуманы эти все классы?

std::future — это недоразумение. Там даже нельзя запустить задачу в своем пуле потоков.

Потому что это не приводит к undefined behavior. Если запустить 2 операции параллельно, то возможно лишь 2 исхода:


  1. Обе выполнятся.
  2. Одна перетрет действие другой.

Других исходов нет.


Можно подойти с другой стороны. Внутри каждой конструкции присутствует мьютекс, который реализует отношение happens-before при обращении к общей памяти. А значит, никакого data race с точки зрения модели памяти там нет.

Ошиблись с сайтом. Бывает.

Лично мне не хватило когнитивного ресурса их всех понять, а главное понять зачем они.

Писать проще, чем читать, я понимаю.


Разумеется, если этого не знать, то можно написать целый цикл статей про то, как попытаться состряпать это на C++. Но, например, в JS есть generator functions.

А в Хаскеле есть монады!


И Путин её одобрил, как я понимаю?

Без одобрения Путина данная статья, безусловно, не появилась бы на свет.

Надо подумать, как это лучше всего сделать. Это непростой вопрос, т.к., фактически, он затрагивает аспекты дизайна и проектирования систем вообще. Как правило, это достигается на опыте написанием своего прототипа или модуля с нуля. Хотя есть и толковые книжки, которые разъясняют это подробно.


Здесь же мы имеем дело со взаимодействием объектов в многопоточной среде. Возникающие проблемы осознаются только тогда, когда с ними непосредственно сталкиваешься. Соответственно, статья как раз о том, как справляться с проблемами взаимодействия объектов.


Тут можно посоветовать лишь брать и пробовать. Но я подумаю о том, чтобы описать стандартные паттерны и подходы к такому снаряду.

Микропроцессору всё равно сколько абстракций накручено над задачей, он выполняет голый машинный код.

Только почему-то оказывается, что голому машинному коду не все равно на количество абстракций. И, как показывает практика, чем больше абстракций, тем тормознее код. Часто для производительности приходится их выпиливать.


Если кому-то потребуется написать сложное асинхронное приложение, то вряд-ли он станет разбираться в подобных абстракциях, а скорее возьмет Go или node.js, которые автоматически конвертируют исходный код в 'сопрограммы'.

Код не может автоматически конвертироваться в сопрограммы. Он должен быть написан с учетом текущей модели исполнения. Если это сопрограммы, то будут использоваться преимущество сопорграмм.


Я подозреваю статья родилась следующим образом: человек пришел в Яндекс, поглядел, удивился, «вау, как тут всё круто!»

Вы просто не представляете, как сильно вы ошибаетесь. Причем везде.


Статья родилась как результат анализа и обобщения различных подходов, кодовых баз и опыта. А заниматься шапкозакидательством — много ума не надо.

Обычно обобщенная базовая функциональность покрывается изрядным количеством тестов, чтобы потом не было мучительно больно.

Информация

В рейтинге
Не участвует
Дата рождения
Зарегистрирован
Активность