如何避免多个线程同时访问ets的资源竟争问题

258 views
Skip to first unread message

yl

unread,
Aug 24, 2010, 10:29:32 PM8/24/10
to erlang-china
问题很常见,但一下子懑了,假设两个线程A,B对ets表的操作

A
------------------------
读取记录r1
|
|                                B
|                                ------------------------------------------------
------------------------         读取记录r1(脏读)
更新记录r1                 |
                                ---------------------------------------------------
                               更新记录r1   <------------------- 覆盖A线程操作


Erlang号称解决此类问题易如反掌,在下愚昧,始终未明白,请明白人赐教解决方案.

Feng Yu

unread,
Aug 24, 2010, 10:45:39 PM8/24/10
to erlang...@googlegroups.com
ETS内部有读写锁的。基于行锁的,所以不存在上面的问题。

专注 高性能容错分布式服务器的研究和实现
http://blog.yufeng.info


2010/8/25 yl <hit...@gmail.com>

yl

unread,
Aug 24, 2010, 11:04:27 PM8/24/10
to erlang...@googlegroups.com
谢谢老大,是如下这种情况,也不可能发生问题吗?
ets:lookup后会自动锁行直到进程结束?




A
------------------------
R1=ets:lookup(tabs,Key)
.....(上厕所)
......                                B
......                                ------------------------------------------------
------------------------             R1=ets:lookup(tabs,Key)(脏读)
ets:delete(tabs, Key)       .......
                                     ---------------------------------------------------
                                     更新记录r1   <------------------- 覆盖A线程操作

刘金鑫

unread,
Aug 24, 2010, 11:41:26 PM8/24/10
to erlang...@googlegroups.com
我认为这样的设计本身就是不是特别好,让一个进程单独处理写,而其他进程处理读,是否更加有利?
--
刘金鑫
QQ:1425579437

Feng Yu

unread,
Aug 24, 2010, 11:44:53 PM8/24/10
to erlang...@googlegroups.com
同意楼主的说法。 上面的编程方式完全无视erlang的设计思想,硬套c的做法。不过ets:safe_fixtable能部分解决这个问题。

专注 高性能容错分布式服务器的研究和实现
http://blog.yufeng.info


2010/8/25 刘金鑫 <adria...@gmail.com>

刘金鑫

unread,
Aug 24, 2010, 11:53:19 PM8/24/10
to erlang...@googlegroups.com
老大,没太明白你说的意思,哪样是C的做法,哪样是erlang的设计思想?
--
刘金鑫
QQ:1425579437

刘金鑫

unread,
Aug 24, 2010, 11:56:20 PM8/24/10
to erlang...@googlegroups.com
你是否这个意思:Erlang本身是不推荐共享内存的设计做法,让两个进程去竞争资源写操作是不符合思想的
--
刘金鑫
QQ:1425579437

yl

unread,
Aug 25, 2010, 12:01:54 AM8/25/10
to erlang...@googlegroups.com
"让一个进程单独处理写,而其他进程处理读"
这样本质上还是要自己实现锁机制

刘金鑫

unread,
Aug 25, 2010, 12:08:24 AM8/25/10
to erlang...@googlegroups.com
仔细考虑的确这样的设计没有避免资源竞争。资源竞争是本身业务的需求不是设计就能能避免掉的
--
刘金鑫
QQ:1425579437

Feng Yu

unread,
Aug 25, 2010, 12:26:42 AM8/25/10
to erlang...@googlegroups.com
我是建议把锁尽量的推给进程,让进程的消息队列为你串行化请求,这样就避免了并发。 而Erlang的进程锁做了大量的优化,你可以放心的让otp团队的人去做的更好。

yl

unread,
Aug 25, 2010, 2:38:28 AM8/25/10
to erlang...@googlegroups.com
ok就是它了,gen_fsm!!!

一抗 卓

unread,
May 17, 2013, 2:15:56 AM5/17/13
to erlang...@googlegroups.com
这个问题erlang本身应该是无法解决的。脏读必然发生。只有通过单进程写,多进程读也无法避免脏读,唯一的办法就是读写都交给一个进程代理。

在 2010年8月25日星期三UTC+8上午10时29分32秒,打倒独立派写道:

Lihe Wang

unread,
May 17, 2013, 6:03:43 PM5/17/13
to erlang...@googlegroups.com
ets:safe_fixtable

一般的需求都满足的,ets:foldr默认启用。

读写锁的问题不是erlang无法解决,是解决的方法是进程消息排序。如果erlang的进程之间争用,就创造一个进程做读写,其他进程与该进程用消息交互。


--
您收到此邮件是因为您订阅了 Google 网上论坛的“Erlang China”论坛。
要退订此论坛并停止接收此论坛的电子邮件,请发送电子邮件到 erlang-china...@googlegroups.com
要查看更多选项,请访问 https://groups.google.com/groups/opt_out。
 
 

卓一抗

unread,
May 17, 2013, 8:49:46 PM5/17/13
to erlang...@googlegroups.com
如果我的表很大,访问的客户端进程非常多,但是服务器进程又只有一个,是不是会出现服务进程大量消息得不到处理的情况。

因为erlang不是绝对公平的调度吗,这样关键的进程只能分到一份进程执行时间,会不会有问题?

hpyhacking

unread,
May 17, 2013, 8:52:47 PM5/17/13
to erlang...@googlegroups.com
如若真是这样就应该将这些进程分离,数据爆炸不是光靠语言机制就可以解决的,更多的是需要更具目前的状况进行合理的分离。

----
Happy Hacking !!!

Lihe Wang

unread,
May 17, 2013, 11:46:03 PM5/17/13
to erlang...@googlegroups.com
hpyhacking的说法没错,但是erlang的解决方案整体上更为直接。解决多写问题,erlang的单进程读写方案退化为普通的读写锁,基本上可以达到写锁的性能极限,因为没有加锁解锁的时间。至于读,可以去掉一些一致性的要求,弱化后快速完成。因为erlang内部数据是不可修改的,在收到消息后,可以启动一个新进程对现有数据进行读取,新进程实际上得到了现有数据的一个快照,并被分配到其他cpu上进行操作。而读进程随后处理新的读写请求。但是可以做快照的数据集可能ets就不合适了,ets只对update等修改操作原子化,所以,应该使用list或是其他的。具体的需要看数据集的实现方式了。

卓一抗

unread,
May 18, 2013, 10:44:44 AM5/18/13
to erlang...@googlegroups.com
假设一下场景:
有1万条数据存在ets表,设其key为1至10000,同时会有大概8000个客户端进程来随机选择这1万条数据的某一条进行下图的更新操作,当然他们可能选择到重复的id,但系统必须保证更新记录的正确性,也就是只有A能更新,B将发现数据已被修改后给出错误。

A
------------------------
读取记录r1
|
|                                B
|                                ------------------------------------------------
------------------------         读取记录r1(脏读)
更新记录r1                 |
                                ---------------------------------------------------
                               更新记录r1   <------------------- 覆盖A线程操作

a.这里当然不能直接读ets,因为ets的读和更新过程无法做到原子操作
b.可以用一个进程来代理这个ets表,把读并更新都交给这个进程代理,逻辑上就正确了。
c.一个进程如果忙不过来,我可以用200个进程来做这个事情,按id rem 200取到相应的进程去执行,这样及时不同额客户进程需要操作相同的id,也可以模到同一个进程去处理,所以数据肯定会正确。

这样是不是符合erlang思维的解决办法?有没有更好的方法?

Lihe Wang

unread,
May 18, 2013, 8:36:15 PM5/18/13
to erlang...@googlegroups.com
我晕,你这是非常具体的要解决方案了,但是,这种东西是一个论坛搞不定的了,除非你们公司把能解决这个问题的人,比如说我,嘻嘻,长时间的聘过去,一直的跟踪调研才行。原因是这样,不是我不能一下子给出方案,而是方案有很多,但我并不清楚具体的数据状态,而且这种东西随着开发和用户状态的变化也在跟着变化,没有任何一个方法能满足所有的要求,可以参考一下cap理论,只能在需求上取舍。

针对你现在的问题,我要问一下,读和写的数量是相当的,还是大部分是读少量写,或是相反?单核的性能能满足所有的读开销或是写开销么(一个进程只能用一个核对吧)?读碰撞概率,写碰撞概率各是多少,读写冲突又是多少?数据变换函数的单一性和计算需求是多少?然后,不要动不动就想着弄两百个相同的进程做任务分派,那种东西其实是实现了三分之一不到的任务调度,是很有难度的并且水很深的,操作系统的一部分,并且是多线程时代开发被常用(并且大部分中国程序员也用不好的)烂方法,当然更重要的,erlang和操作系统都已经两层实现了,用erlang的人再实现一层干嘛????

我觉得我可以做的是帮你找到思维方法,具体的方案,参考我上面给的问题,做取舍,重新设计。如果你能做到,那么,你们的团队就有一个能随着变化重新设计的人了。加油喽~~~~

Lihe Wang

unread,
May 18, 2013, 8:56:40 PM5/18/13
to erlang...@googlegroups.com
我觉得很好玩,我找到了你github上的博客,理论你已经都会了很多了啊,为什么还解决不了这个问题呢?好好看看自己的博客就行了,嘻嘻。


在 2013年5月18日下午10:44,卓一抗 <zhuoy...@gmail.com>写道:

卓一抗

unread,
May 18, 2013, 10:31:57 PM5/18/13
to erlang...@googlegroups.com
其实应用场景是实现一个coc中的选择战斗力相近的用户来pvp的逻辑细节,只是存储假定必须是ets,因为这篇帖子讨论的是ets,ets本身没有提供CAS的原子操作。如果Redis之类的本身就支持CAS的就是另一种做法。

实际的情况是读多写少,这若干行有两种状态A和B,需要从状态属性为A的行中筛选一个,这时候需要读多个,然后对筛选后的1个进行更新操作,将其置为B。因此读多写少。

在筛选和更新这两个操作之间也不是原子操作,因此甚至是状态集合里的成员在被筛选的时候都已经有可能被其他进程更新为状态B了,

问题是在于同一时间有可能多个客户端都选中了同一行,但更新操作必须只能被做一次,之后尝试更新的需要能会返回错误。

当然是需要尽可能的利用多核,因为一个erlang进程最多只能占用一个cpu,所以猜想一下开(cpu核数+1)个服务进程来做`读取+更新`操作比较合适。

这样实际的读取+更新操作就被服务进程串行化了,保证了逻辑的正确,也可以更好的并行,利用cpu核数。

考虑到筛选的行有可能状态已经被改变了,我可以多选几个发给服务进程,它尝试成功更新一个就返回即可,减少一点由于发生冲突,而需要重新筛选计算的可能。

至于数据更新变换函数,其实只是判断一下状态是否是期望的状态,如果是则更新,不是则返回错误,标准的CAS操作。

=======

@Lihe: 因为做erlang没什么经验,大多是纯想法,需要实际的模拟测试才知道是不是靠谱,我是成都的开发者,erlang不是很活跃,只有在论坛上求帮助。



Lihe Wang

unread,
May 19, 2013, 7:30:25 PM5/19/13
to erlang...@googlegroups.com
呵呵,我是北京的,但是北京的erlang也没怎么活跃啊,我想换个工作,都没有招erlang的,虽然我觉得“招一个会xx语言的程序员”这种想法很傻逼,但是完全没有的话心里也怪怪的。北京公司多,但是程序员数量也多,程序员不受重视,好的技术不用也没关系,全都靠人堆,我有时觉得成都挺好的,听说程序员都是大爷,哈哈。

我看了你对问题的描述,嗯,劝你重新想一下你的问题模型,否则会掉进“手里拿着锤子,看啥都像钉子”的陷井。如果我没理解错,你的问题是一个分配问题,而不是一个存储问题,而后者才需要解决读写冲突。你问了一大堆如何优化读写的问题,但是实际上在解决错误的问题,配对问题不需要遍历和单独加锁,而是“全锁”就行了。我假设所有的p不分级,简单一点,将所有待参战的p全都存在一个,比如说列表吧,列表存在一个通用服务器里吧。然后这个gen_server可以接受p进入和离开。gen_server周期检测所有的p,做成最大化可对战的一个数对组,挨个安排对战,剩下的没成对儿的,继续存成列表交给gen_server自身循环。这样,每一次做配对儿时,全锁就行了,没有读写冲突。如果配对时间太长,可以启用新进程,新进程得到了全部p,gen_server暂时清空,接受进入离开请求,新进程将剩余发回来就好了,没有啥问题。至于分级问题,配对函数适当调整应该可以搞定,配对过程中有p离开了,当做一致性问题,解决的话随你喜欢,比如让没离开的p重新回到队列并安排一个高一点的配对优先级就成了。

希望可以解决你的问题。

卓一抗

unread,
May 19, 2013, 9:31:36 PM5/19/13
to erlang...@googlegroups.com
以前我一直觉得,如果客户端进程太多,比如几万,但是服务器进程很少,比如只有1个,会有服务进程得不到执行的问题,因为erlang公平调度,每个进程分配到的执行时间一样,这种情况下就只有多启动服务进程来多分到执行流。

但是似乎我陷入了这个误区,犯了过早优化的错误,其实到底服务进程执行的过来与否还是要实际运行来看,或者模拟运行。

你说的方法比较有用,反正就是单进程处理其实不需要锁,但无法并行,因为所有都分配计算都在这个进程内部,如果如果分配计算比较耗时会卡顿,出现这个服务进程消息处理不过来的情况。而我想的是让数据大家都可见,可以在客户进程内部进行计算和分配,但是由一个进程来解决分配冲突,这样会比较合理,并发度也高。

程序员从来都不是爷,不过成都美女多,如果未婚可以考虑来成都发展,哈哈。


郎咸武

unread,
May 20, 2013, 2:16:24 AM5/20/13
to erlang-china
 参加战斗的和不参加战斗的为什么不分给存储。
这样即使两个人同时选择一个数据,最终一个人成功一个人失败而已,在一定程度上也不会出现两个人写一个数据的问题。
只为成功找方法,不为失败找理由

郎咸武

unread,
May 20, 2013, 2:17:44 AM5/20/13
to erlang-china
参加战斗的和不参加战斗的为什么不分开存储。
这样即使两个人同时选择一个数据,最终一个人成功一个人失败而已,在一定程度上也不会出现两个人写一个数据的问题。


--
只为成功找方法,不为失败找理由

Lihe Wang

unread,
May 20, 2013, 8:52:29 AM5/20/13
to erlang...@googlegroups.com
先介绍个妹子呗,哈哈。


在 2013年5月20日上午9:31,卓一抗 <zhuoy...@gmail.com>写道:
Reply all
Reply to author
Forward
0 new messages