Mnesia сделка сделает именно эту вещь для вас. Это то, что является транзакцией, если вы не делаете грязные операции. Так что просто поместите ваши операции записи и чтения в одну транзакцию, а mnesia сделает все остальное. Все операции в одной транзакции выполняются как одна атомарная операция. Уровень изоляции транзакции Mnesia - это то, что иногда называют «сериализуемым», т. Е. Самый сильный уровень изоляции.
Edit:
Кажется, вы упустили один важный момент о параллельных процессах в Erlang. (Чтобы быть справедливым, это верно не только в Erlang, но и в любой действительно параллельной среде, и когда кто-то спорит, это не совсем параллельная среда.) Вы не можете различить, какие действия происходят первыми, а какие - вторыми, если вы не выполняете некоторую синхронизацию. Единственный способ сделать эту синхронизацию - передача сообщений. Вы гарантировали только одну вещь о сообщениях в Erlang, порядок сообщений, отправленных одним процессом другому процессу. Это означает, что когда вы отправляете два сообщения M1
и M2
из процесса A
в процесс B
, они приходят в том же порядке. Но если вы отправите сообщение M1
с A
на B
и сообщение M2
с C
на B
, они могут прийти в любом порядке. Просто потому, что как вы можете узнать, какое сообщение вы отправили первым? Еще хуже, если вы отправляете сообщение M1
с A
на B
, а затем M2
с A
на C
и когда M2
приходит на C
отправляет M3
с C
на B
вы не гарантировали, что M1
прибудет на B
до M3
. Даже это произойдет в одной виртуальной машине в текущей реализации. Но вы не можете полагаться на это, потому что это не гарантировано и может измениться даже в следующей версии виртуальной машины только из-за реализации передачи сообщений между различными планировщиками.
Это иллюстрирует проблемы упорядочения событий в параллельных процессах. Теперь вернемся к сделке Mnesia. Mnesia сделка должна быть без побочных эффектов fun
. Это означает, что никакое сообщение не может быть отправлено извне из транзакции. Таким образом, вы не можете сказать, какая транзакция начинается первой и когда начинается. Единственное, что вы можете сказать, если транзакция прошла успешно и они упорядочены, вы можете определить только по ее эффекту. Когда вы думаете об этом, ваше тонкое разъяснение не имеет смысла. Одна транзакция будет считывать все ключи в атомарной операции, даже если она реализована как чтение одного ключа за другим в реализации транзакции, и ваша операция записи также будет выполняться как атомарная операция. Вы не можете сказать, была ли запись в 4-й ключ во второй транзакции произошла после того, как вы прочитали 1-й ключ в первой транзакции, потому что там это не наблюдается снаружи. Обе транзакции будут выполняться в определенном порядке как отдельная атомарная операция. С внешней точки зрения все ключи будут прочитаны в один и тот же момент времени, и это задача mnesia - заставить его. Если вы отправляете сообщение изнутри транзакции, вы нарушаете свойство транзакции mnesia и не удивляетесь, что это будет вести себя странно. Чтобы быть конкретным, это сообщение может быть отправлено много раз.
Edit2:
Если вы раскрутите даже несколько из этих процессов, вы увидите, что очень
быстро a, b и c находятся в состоянии, когда их значения не равны 10.
Мне любопытно, почему вы думаете, что это произойдет, или вы проверили это? Покажите мне ваш тестовый пример, и я покажу мой:
-module(transactions).
-export([start/2, sum/0, write/0]).
start(W, R) ->
mnesia:start(),
{atomic, ok} = mnesia:create_table(test, [{ram_copies,[node()]}]),
F = fun() ->
ok = mnesia:write({test, a, 10}),
[ ok = mnesia:write({test, X, 0}) || X <-
[b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]],
ok
end,
{atomic, ok} = mnesia:transaction(F),
F2 = fun() ->
S = self(),
erlang:send_after(1000, S, show),
[ spawn_link(fun() -> writer(S) end) || _ <- lists:seq(1,W) ],
[ spawn_link(fun() -> reader(S) end) || _ <- lists:seq(1,R) ],
collect(0,0)
end,
spawn(F2).
collect(R, W) ->
receive
read -> collect(R+1, W);
write -> collect(R, W+1);
show ->
erlang:send_after(1000, self(), show),
io:format("R: ~p, W: ~p~n", [R,W]),
collect(R, W)
end.
keys() ->
element(random:uniform(6),
{[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b],[c,b,a]}).
sum() ->
F = fun() ->
lists:sum([X || K<-keys(), {test, _, X} <- mnesia:read(test, K)])
end,
{atomic, S} = mnesia:transaction(F),
S.
write() ->
F = fun() ->
[A, B ] = L = [ random:uniform(10) || _ <- [1,2] ],
[ok = mnesia:write({test, K, V}) || {K, V} <- lists:zip(keys(),
[10-A-B|L])],
ok
end,
{atomic, ok} = mnesia:transaction(F),
ok.
reader(P) ->
case sum() of
10 ->
P ! read,
reader(P);
_ ->
io:format("ERROR!!!~n",[]),
exit(error)
end.
writer(P) ->
ok = write(),
P ! write,
writer(P).
Если это не сработает, это будет действительно серьезная проблема. Есть серьезные приложения, в том числе платежные системы, которые полагаются на это. Если у вас есть тестовый пример, который показывает, что он не работает, сообщите об ошибке по адресу erlang-bugs@erlang.org