Pull to refresh

Comments 28

Очень интересно — пошел пробовать :)
Но как быть при обновлении счет с «условиями гонки» (race condition)? Если, например, придет сразу 2 перевода? Корректно ли обновится счет?
Насколько я понял — это статья начального ознакомительного уровня. Поэтому здесь все упрощено и такие вопросы не учитываются:)
я вот тоже пока не понимаю, является ли handle_call «атомарным». перерыл много статей, везде хэлло вордлы, надо видимо писать тесты самому…

а вцелом, сам армстронг, например, предлагает использовать т.н. транзакционнную память armstrongonsoftware.blogspot.com/2006/09/pure-and-simple-transaction-memories.html
но у нее тоже есть свои нехилые заморочки
и пишут о ней весьма разное mags.acm.org/queue/200809/?pg=49
Читал про транзакционную память. В первую очередь меня смутило, что надо будет «руками» отслеживать транзакции — шикарный источник трудноуловимых ошибок. Непонятно, как менять сразу несколько переменных — так чтобы транзакция была целостной. Может быть я избаловался ACID-базами, но иначе какие это транзакции? Тем более раз говорим про банковскую систему, то там обязательно надо делать транзакции: допустим у вас перевод с одного счета на другой — как ее будете контролировать, чтобы человек не ушел в минус или сумма счета была правильной?
по старинке, локами ))
да не, я и говорю, вот и не ясно…

эрланг ведь, как и любой другой язык, не решает эти вопросы, он просто предлагает определенные удобства и формализации. все то же самое можно делать и на stackless python, и на jocaml, и на чем угодно, но стаклесс — костыль, а эрланг приспособлен by design.
а проблема изменения глобальных state'ов пока остается на разработчике в любом случае
Я согласен, что эрланг более подходящ для таких задач.
Но одновременно вылезает масса других проблем в более простых местах. И для их разруливания требуются усилия.
По идее глобальных переменных быть не должно — так уж устроен Э. Значит должен быть другой вариант решения этой задачи, более подходящий для этой ситуации. Если его нет, то это проблема дизайна языка, и ее конечно можно решать с помощью разных модулей, но это будут жуткие костыли.
ну почему масса, я вижу реальную проблему только с этим.

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

ну будут те же локи, зато остальное удобно масштабировать и т.д.
эрланг не лучший язык, но зато можно решать актуальные проблемы без костылей и достаточно стабильно, за нативные микротреды можно многое простить )
Все вызовы gen_server выстраиваются в очередь и обрабатываются последовательно. То есть handle_call получается атомарный.
Любое общение между процессами осуществляется через очередь сообщений. handle_call/cast/info — просто очень высокая надстройка над обыкновенным оператором "!", они все точно так же последовательно выхватываются из очереди. «Сразу два» просто-напросто не бывает :)
Другое дело, если проверка и изменение разнесены на два отдельных обращения к процессу. Тогда, конечно, кто-нибудь может вклиниться; но для этого надо соответствующим образом планировать интерфейс модуля.
Все вызовы gen_server выстраиваются в очередь и обрабатываются последовательно. Поэтому гонок быть не должно, сколько не вызывай параллельно gen_server.
Возможно я неправильно вас понял, тогда поправьте :)
Спасибо за наводку на модуль dict.
Как он работает? Ведь его работа противоречит сразу 2 базовым принципам Erlang: отсутствие общей памяти и неизменяемость переменных.
Вот не знаю, надо читать документацию…
нет, все нормально )

D = dict:new(),
io:format(«D:~n~p~n», [D]),
D1 = dict:store(«foo», bar, D),
io:format(«D:~n~p~n», [D]),
io:format(«D:~n~p~n», [D1]).
Мммм… не очень понял, что вы ожидали и что получили :)
Это не «подкол», это вопрос — я еще совсем мало понимаю в Эрланге :)

Попробовал в консоли:
Eshell V5.6.3 (abort with ^G)
(erl@laptop)1> D=dict:new().
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}

(erl@laptop)2> D.
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}

(erl@laptop)3> dict:store('test', 123, D).
{dict,1,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],
[[test|123]],
[],[],[],[],[],[],[],[],[],[],[]}}}

(erl@laptop)4> D.
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}


После такой процедуры сам D не изменился:
(erl@laptop)6> dict:fetch('test', D).
** exception error: no function clause matching dict:fetch_val(test,[])


Тогда как быть с конкуренцией? И получается, dict — это не хранилище, а просто переменная с гибкой структурой, такая же как и все остальные в Эрланге. А значит, у каждого процесса будет свой реестр банковских счетов. Их действительно можно использовать асинхронно. Но теперь с асинхронностью стало все еще непонятнее. Получается, что каждый счет будет иметь массу значений — и одно из них истинное? Или я совсем запутался?
ой, не туда запостил, см ниже )
Эти принципы прекрасно соблюдаются. Все модули, работающие со сложными структурами типа словарей и деревьев, не изменяют структуру, которую им дают на входе, а разбирают ее на куски и строят и возвращают совершенно новую.
ожидал, что не изменится, и он не изменился )
>dict — это не хранилище, а просто переменная с гибкой структурой, такая же как и все остальные в Эрланге
ага

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

Мне сейчас видится такая модель: Есть процесс отвечающий за список счетов — он ведет этот dict. В каждом дикте одним из элементов является ссылка на процесс, который отвечает за один конкретный аккаунт. Этот процесс может выглядеть так:

cnt(Name, Count) ->
    SName = Name,
    SCount = Count,
    io:format("Before rec. Name: ~p, Count: ~p", [SName, SCount]),
    receive 
        {init, N, C} ->
            cnt(N, C);
        {inc, C} ->
            cnt(SName, SCount - C);
        status ->
            io:format("Name: ~p, Count: ~p~n", [SName, SCount]),
            cnt(SName, SCount);
        stop ->
            io:format("Proc stop", []);
        _ ->
            io:format("Default msg", []),
            cnt(SName, SCount)
    end.

С ним можно работать стандартно:
1>S1 = spawn(module, cnt, ["", 0]).

2> S1! {init, «Сид», 100}.
Before rec. Name: «Сид», Count: 100{init,«Сид»,100}

4> S1! {inc, 20}.
Before rec. Name: «Сид», Count: 80{inc,20}

5> S1! status.
Name: «Сид», Count: 80


Но по идее, это все должно делаться не на чистом Эрланге, а на OTP. Но как?
Например, перечисление денег надо сделать синхронным и хорошо бы транзакционным.
черт опять не туда запостил )
и, кстати, acid все же есть ) en.wikipedia.org/wiki/Mnesia#Transactions
тут получается узкое место — один процесс, хранящий все аккаунты. мне кажется, лучше делать просто рассылку запроса процессам, отвечающим за аккаунты, и ответит нужный процесс. т.е. мы можем разбить список аккаунтов на несколько процессов (на кластер например), которые будут уже контролировать сами процессы-аккаунты.
как-то так
Само собой за каждый аккаунт отвечает свой процесс.
Но как их искать в памяти? Я не придумал пока ничего лучше некоторого процесса-справочника, который будет хранить список всех аккаунтов. Но при этом он станет узким местом. Если его использовать только как справочник один раз.
То есть, если кто-то хочет работать с каким-то счетом, то он должен обратиться к этому главному процессу и получить у него ссылку на процесс-акканут. После этого, уже можно будет работать непосредственно с аккаунтом, уже без обращений к главному. Причем здесь же возможен вариант, когда процесс-счет не существует вообще либо не загружен из БД. Тогда главный просто загружает процесс-акканут и возвращает его. Для работающего со счетом все прозрачно.

Не очень понял вашу мысль
> просто рассылку запроса процессам, отвечающим за аккаунты, и ответит нужный процесс.
То есть опросить все процессы? Не будет ли это лишним оверхедом? Может Эрланг содержит какие-то нативные средства для таких операций? Послать сигнал процессам, удовлетворяющим определенной маске. Но все равно пока не ясно, как хранить такой список? Даже если в виде списка. (Кстати, у меня какое-то предубеждение в жуткой неэффективности такой схемы работы со списками).
>То есть опросить все процессы?
нет, опросить «справочник», но мы просто разделяем его на несколько частей-процессов, которые и опрашиваем, и он перестает быть узким.

т.е. например: есть два процесса, в каждом по половине справочника. посылаем им запросы, они *параллельно* проверяют у себя аккаунт, и дальше уже по-разному можно. отвечает нашедший справочник или уже сам процесс-аккаунт и т.д.
Т.е. справочник все равно становиться неким state-var нашего вычислительного процесса? Если так, то получаем объектный подход, вместо обещанного функционального, где понятия состояния не существует впринципе.

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

Объясните пожалуйста, уважаемые гуру функционального языка ерланг, как это все реализовываеться на практике (я пока только начал разбираться в ФП).
ну я, мягко говоря, не гуру эрланга и фп, но могу сказать, что чистое stateless фп (как и любая парадигма, религия и т.д.) идеально применимо к решению сферических задач в вакууме, а не реальных-полезных на практике, такие дела.
как обычно, нужно комбинировать…

дело в том, что эрланг дает нам out-of-the-box удобности типа легких потоков, эффективного ipc, распределение между ярдами, компами, динамика среды, упрощение построения протоколов, слежение за процессами и т.д., все остальное лежит на девелопере, от этого никуда не деться )
магическое избавление от стейта это такой хитрый маркетинг, ну типа как ооп.

никакая парадигма не избавляет от того, что нужно как-то решать конкурентный доступ к данным не теряя сильно в производительности и предсказуемости.
UFO just landed and posted this here
Давайте пожалуйста ссылку на оригинальную статью. Перевод иногда сложно понять, но всеравно спасибо!
Ссылка всегда есть в конце статьи. Жми на «Mitchell Hashimoto»
Sign up to leave a comment.

Articles