我如何希望我已经开始提出我的问题
拿一张带有26个键的表,a-z并让它们具有整数值。 创建一个过程,哎呀,一遍又一遍地做两件事
如果您启动其中一些流程,您会很快看到 a , b 和 c 处于某种状态他们的值不总和为10.我相信没有办法要求mnesia“在开始写入(或读取)之前锁定这3条记录”,只能让mnesia将记录锁定为它到达了他们(可以这么说),它允许记录集的值违反我的“必须加总到10”的约束。
如果我是对的,这个问题的解决方案包括
想法?
好的,我是一个雄心勃勃的Erlang newbee,很抱歉,如果这是一个愚蠢的问题,但是
我正在构建一个特定于应用程序的内存分布式缓存,我需要能够在一个事务中编写 Key,Value 对的集合,并在一个事务中检索值集。换句话说,我需要 1)将40个键,值对写入高速缓存,并确保在此多键写入操作期间没有其他人可以读取或写入这40个键中的任何一个;和, 2)在一次操作中读取40个键并获得40个值,知道从读取操作开始到结束的所有40个值都没有变化。
我能想到这样做的唯一方法是将整个表锁定在fetch_keylist([ListOfKeys])的开头或write_keylist的开头([KeyValuePairs],但我不想这样做因为我有很多进程同时进行自己的multi_key读写操作,而且我不想在任何进程需要读取/写入相对较小的记录子集时锁定整个表。
帮助?
试图更清楚:我不认为这只是关于使用香草交易
我想我问的是一个比这更微妙的问题。想象一下,我有一个进程,在一个事务中,迭代10个记录,锁定它们。现在假设这个过程开始但在它迭代到第3个记录之前另一个过程更新了第3个记录。就事务而言,这将是正常的,因为第一个进程尚未锁定第三个记录(尚未),而OTHER进程修改它并在第一个进程到达之前将其释放。我想要的是保证,一旦我的第一个进程启动,在第一个进程完成之前,没有其他进程可以触及10条记录。
问题已解决 - 我是个傻瓜......我猜... 谢谢你的病人,特别是Hynek -Pichi- Vychodil! 我准备了我的测试代码以显示问题,而我可能实际上重现了这个问题。然后我简化了代码的可读性,问题就消失了。我无法再次重现这个问题。这对我来说既尴尬又神秘,因为我有几天这个问题。 mnesia也从不抱怨我在事务之外执行操作而且我的代码中没有任何脏事务,我不知道我是如何将这个bug引入我的代码的!
我已经将隔离的概念强加到我的头脑中,并且不会怀疑它是否会再次存在。
感谢您的教育。
实际上,事实证明问题是在事务中使用围绕mnesia操作的try / catch。有关详情,请参阅here。
答案 0 :(得分:2)
Mnesia交易将为您完成这件事。除非你做脏操作,否则这是什么交易。因此,只需将您的写入和读取操作放在一个事务中,mnesia就会休息。一个事务中的所有操作都是作为一个原子操作完成的。 Mnesia事务隔离级别有时被称为“可序列化”,即最强的隔离级别。
似乎你错过了关于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
fun
-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).
{{1}} {{1}} {{1}} }。即使它将在当前实现中的一个VM中发生。但是你不能依赖它,因为它不能得到保证,即使在下一版本的VM中也可以改变,因为不同调度程序之间的消息传递实现了。
它说明了并发进程中事件排序的问题。现在回到mnesia交易。 Mnesia交易必须是免费的副作用{{1}}。这意味着可能没有任何消息从交易发送到外部。因此,您无法确定哪个事务首先启动,何时启动。如果交易成功并且他们订购您只能通过其效果确定,那么您可以判断。当你考虑到这一点时,你的微妙澄清毫无意义。一个事务将读取原子操作中的所有键,即使它实现为在事务实现中逐个读取一个键,并且您的写操作也将作为原子操作执行。在第一次交易中读取第一个密钥后,您无法判断第二个交易中的第四个密钥是否发生,因为从外部无法观察到。两个事务都将按特定顺序执行,作为单独的原子操作。从外部的角度来看,所有按键都将在同一时间点读取,并且强制它的是mnesia的工作。如果您从事务内部发送消息,则违反了mnesia事务属性,您不会感到惊讶,它会表现得很奇怪。具体而言,此消息可以多次发送。
如果您启动这些过程中的一些,您会看到它 快速a,b和c处于其值不等于10的状态。
我很好奇为什么你认为它会发生或你测试过它?告诉我你的测试用例,我会告诉我的:
{{1}}
如果它不起作用,那将是非常严重的问题。有严肃的应用程序,包括依赖它的支付系统。如果您的测试用例显示已损坏,请在erlang-bugs@erlang.org上报告错误
答案 1 :(得分:1)
您是否尝试过 mnesia Events ?您可以让读者订阅mnesia的Table Events
特别write
事件,以免中断写入过程。通过这种方式,mnesia只是不断地将实时写入的内容的副本发送给另一个进程,该进程在任何时候检查值是什么。看看这个:
subscriber()-> mnesia:subscribe({table,YOUR_TABLE_NAME,simple}), %% OR mnesia:subscribe({table,YOUR_TABLE_NAME,detailed}), wait_events().现在,您将分析器生成为如下过程:
wait_events()-> receive %% For simple events {mnesia_table_event,{write, NewRecord, ActivityId}} -> %% Analyse the written record as you wish wait_events(); %% For detailed events {mnesia_table_event,{write, YOUR_TABLE, NewRecord, [OldRecords], ActivityId}} -> %% Analyse the written record as you wish wait_events(); _Any -> wait_events() end.
spawn(?MODULE,subscriber,[]).
这使得整个过程在没有任何进程被阻止的情况下运行,mnesia不需要锁定任何表格或记录,因为现在你拥有的是writer
进程和analyser
进程。整件事将实时运行。请记住,如果您希望在订阅者wait_events()
接收正文中通过模式匹配它们,可以使用许多其他事件。
可以构建heavy duty gen_server
或完整application
,用于接收和分析您的所有mnesia事件。拥有一个有能力的用户通常比许多失败的事件用户更好。如果我理解你的问题,这个unblocking
解决方案符合您的要求。
答案 2 :(得分:-1)
mnesia:带有写锁的读/ 3似乎已经足够了。
Mnesia的事务是通过读写锁实现的,并且锁是格式良好的(保持锁定直到事务结束)。因此隔离级别是可序列化的。
只要您通过主键访问,锁的粒度就是每条记录。