LINUX.ORG.RU

Как вы пишете асинхронный, интерактивный код?

 


0

3

Имеется привычная ситуация: UI, в котором пользователь бездумно жмакает кнопочки, которые в свою очередь запускают асинхронные/параллельные задачи. Это может быть как и GUI, так и сайт, посылающий запросы серверу.

А теперь начинается интересное:

  1. Любое действие должно быть отменяемым (не убивание потока, а штатное завершение). Большинство async кода что я видел, не поддерживает такую простую фичу. Видимо вебсерверам это не интересно. Даже в Go, с его горутинами, это достигается велосипедами, а не средствами языка.
  2. Любое действие может быть продолжено. Это немного похоже на предыдущую задачу, но с тем нюансом, что у нас задача представляет собой не loop {}, а цепочку действий.
  3. Задача может общаться с родителем. Запрашивать новые данные, подтверждать действия.
  4. Задача может использовать глобальный контекст. То есть живёт не сама по себе.
  5. Задача может запускать свои асинхронные задачи и на неё распространяются те же требования, что и выше.
  6. Это не должно превратиться в callback hell.

Язык роли не играет. Производительность тоже. Интересует сам алгоритм.

★★★★★

Чёто звучит, как какой-то архитектурный ад, может лучше пачку микросервисов навернуть?

Trunk
()
Ответ на: комментарий от Trunk

Микросервисы не выполняют пункт 4. И обычно не выполняют пункт 2

monk ★★★★★
()
Ответ на: комментарий от alysnix

Но похоже вы доказали их ненужность.

Похоже это доказал все же не я, а вы.

Поскольку,по-вашему, проблема с незавершенным изменением и приходом системы в некорректное состояние может возникнуть только у нубов…

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

В общем-то у такого рода проблем всегда почти одни и те же причины, которые я и озвучил. Либо разработчик был слишком уставший и написал ерунду не задумываясь о том, как такой код будет работать, либо недостаточно опытен чтобы это понять, либо это очень большой проект, достаточно сложный, достаточно гетерогенный, скорее всего с кучей легаси кода на поддержке которой уже особо никто не правит и не разбирается в том как он работает, по типу «работает не трогай», но вот как раз он может и содержать такой код который приведет к похожей ситуации взаимодействуя с вашим кодом. Конечно причин может быть больше.

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

abcq ★★
()
Последнее исправление: abcq (всего исправлений: 1)
Ответ на: комментарий от abcq

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

Ни разу не видел в линуксовых скриптах, чтобы вместо rm использовали trash-put.

monk ★★★★★
()
Ответ на: комментарий от monk

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

cobold ★★★★★
()
Ответ на: комментарий от cobold

Блокировка через syscall действительно может быть, поэтому напрямую использовать FFI (вызовы сишных библиотек) в Erlang и Racket не рекомендуется. Если очень надо, для FFI делается отдельный процесс/поток ОС (node в Erlang, place в Racket).

Проблема со свопом не больше, чем с потоками ОС. В своп приложению уходит не навсегда, а на время чтения страницы памяти. Между чтениями страниц планировщик успевает дать часть времени каждому зелёному потоку.

monk ★★★★★
()
  1. Отменяемость в каком смысле? Просто перестать ждать ответа? Или вернуть все в исходное состояние? Последнее не всегда возможно, энтропия вселенной растёт. По сути нужно наложить довольно жесткие ограничения на то что может делать система чтобы обеспечить выполнение этого требования
  2. При помощи очереди последовательность можно преобразовать в цикл
cobold ★★★★★
()
Ответ на: комментарий от monk

Ну это все к вопросу о том, что это вообще за система и насколько вы щепетильно относитесь к своим данным. В общем-то никто вроде не говорил что это «линуксовые скрипты» (ну или я это как-то пропустил) по мне это был какой-то оторванный от реальности пример который должен был быть примером того, что убивать поток обработки чего-либо извне чревато последствиями.

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

Короче все зависит от конкретной реализации и каких-то превентивных предпринятых мер как мне кажется.

abcq ★★
()
Ответ на: комментарий от abcq

Ну это все к вопросу о том, что это вообще за система и насколько вы щепетильно относитесь к своим данным.

Так вот я про то же. Скрипты имеют все те же проблемы, но их никто не опасается завершать через Ctrl-C и не требует, чтобы cp или rm обладали транзакционностью.

Кстати, проблема с мьютексами имеет решение: http://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html

monk ★★★★★
()
Ответ на: комментарий от monk

node в Erlang

Нода это прям полноценная эрланговая система, для отдельной задачи достаточно port или port driver.

loz ★★★★★
()
Ответ на: комментарий от monk

Так вот я про то же. Скрипты имеют все те же проблемы, но их никто не опасается завершать через Ctrl-C и не требует, чтобы cp или rm обладали транзакционностью.

возмутительно некорректное утверждение.

напишите скрипт, прошивающий ваше устройство, и прервите его посредине. :))) а потом приходите сюда и поделитесь впечатлениями.

alysnix ★★★
()
Ответ на: комментарий от loz

Ну, как я и сказал мьютексов нету.

в java тоже мьютексов нет… но там eсть synchronized… что и есть мьютекс…просто другими словами.

отсутствие какого-либо термина не является признаком отсутствия явления как такового. оно просто может иметь другое название.

alysnix ★★★
()
Ответ на: комментарий от abcq

Кстати даже этот пример, даже в том виде в котором его привел @alysnix можно улучшить, например если учесть тот факт, что поток удаляющий данные имеет права на удаление данных только не далее определенной директории, к которой он имеет права, тогда такой ситуации в принципе не может возникнуть что он вам домашнюю директорию удалит, или какую-то еще

я пытался привести простейший и, думал, нагляднейший пример - почему уничтожение треда в лоб, даже при технически правильном освобождении мьютекса пожет породить проблему… и все принялись обсуждать - как правильно удалять файлы…

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

abcq ★★
()
Ответ на: комментарий от alysnix

Так я же именно про это. Возможность такого скрипта не заставляет писать инструкции пользователю типа «никогда не прерывайте запущенный скрипт, это может привести к катастрофе».

monk ★★★★★
()
Ответ на: комментарий от monk

Возможность такого скрипта не заставляет писать инструкции пользователю типа «никогда не прерывайте запущенный скрипт, это может привести к катастрофе».

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

loz ★★★★★
()
Ответ на: комментарий от alysnix

Так прерывание потока рассматривается вообще не от неизвестной программы, а от своей собственной.

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

monk ★★★★★
()
Ответ на: комментарий от monk

Так прерывание потока рассматривается вообще не от неизвестной программы, а от своей собственной.

«свои собственные программы» бывают только у программистов одиночек. но 99.99% софта написано не ими, а коллективами разноцветных программистов, что никогда не видели друг друга, и не увидят.

и не надо об этом забывать. разработка ПО это вообще-то индустрия, а не хобби.

alysnix ★★★
()
Ответ на: комментарий от loz

Структуры иммутабельны, а состояния процессов нет. Может быть полезно, если надо несколько сообщений отправить так, чтобы между ними от другого аналогичного не прилетело. В mnesia транзакции не просто так придумали

monk ★★★★★
()
Ответ на: комментарий от monk

а состояния процессов нет

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

loz ★★★★★
()
Ответ на: комментарий от loz

Состояния процессов это те же иммутабельные структуры

Состояния процессов меняются сообщениями. Оттого, что при этом создаётся новая иммутабельная структура вместо существующей роли не играет.

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

https://issue.life/questions/35413586

deposit(Amount) ->
    OldBalance = get_bal(),
    NewBalance = OldBalance + Amount,
    set_bal(NewBalance).

Надо, чтобы если deposit одновременно был вызван в разных процессах, то не получилось, что сначала будет в обоих процессах выполнена первая строка, а потом в обоих процессах остальные две. То есть, чтобы между get_bal и set_bal баланс не менялся.

monk ★★★★★
()
Ответ на: комментарий от monk

Так это зависит от того что внутренностей get_bal и set_bal, по твоему примеру очевидно что внутри есть сайд эффекты. Собственно в эрланге это можно решить разными способами взависимости от того что нужно.

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

loz ★★★★★
()
Последнее исправление: loz (всего исправлений: 2)
Ответ на: комментарий от loz

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

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

monk ★★★★★
()
  1. Канцелятион токен или, если нет встроенного, то колхозить таскконтекст-интерфейс с методом «что, продолжаем?». При отсутствии встроенного будут проблемы с библиотечными функциями типа сетевых запросов или запросов к БД. Возможно обёртку писать придётся.
  2. Это как? Запустил задачу, в середине понял, что данные не те, отменил, поправил данные, запустил снова, и чтоб задача пропустила те этапы, для которых ничего не изменилось? Это сложно. Если надо просто возможность приостановить, то см предыдущий пункт в предыдущем пункте метод «что, продолжаем?» делать асинхронным.
  3. В таскконтекст интерфейс добавляем асинхронные методы для общения. Не знаю как с пунктом 6 соотносится…await context.confirm("Точно удалить всё нахрен?") - это коллбэк хелл?
  4. Ну… как бы себе проблемы создаёшь, но в каждой проверке токена надо откатывать всё испорченное. А если параллельно запущена другая таска… ну всё тогда, приплыли. Нужна блокировка, нужно что-то с дедлоками делать.
  5. Используем общий канцелятион токен или таскконтекст-интерфейс композитим из некоего обобщённого «гуитаск», который заведует отменой/приостановкой и который можно передать дочерним таскам в рамках сконструированых для них контекстов.
khrundel ★★★★
()
Ответ на: комментарий от monk

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

loz ★★★★★
()
Ответ на: комментарий от loz

На что люди только не идут, только бы стандартные подходы не использовать.

deposit(Amount) ->
    mutex:lock(),
    OldBalance = get_bal(),
    NewBalance = OldBalance + Amount,
    set_bal(NewBalance),
    mutex:unlock().

И гарантированная корректность deposit без поломки get_bal и set_bal.

monk ★★★★★
()
Ответ на: комментарий от monk

Так это стандартный для Си подход, Эрланг другой язык от слова совсем. Ты еще покритикуй отсутствие циклов.

loz ★★★★★
()
Ответ на: комментарий от loz

Эрланг другой язык от слова совсем.

Если явно не выделять мьютекс, можно процедуру deposit завернуть в поток

run_deposit() -> 
    receive
        Amount ->
                OldBalance = get_bal(),
                NewBalance = OldBalance + Amount,
                set_bal(NewBalance)
    end.

register(deposit_process, spawn(bank, run_deposit, []))

deposit(Amount) -> deposit_process ! Amount

Будет по-эрланговски. Но если надо заблокировать разные ресурсы в транзакции, то всё-равно мьютексами проще.

Ты еще покритикуй отсутствие циклов.

Тоже легко можно сделать при желании.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.