一直耿耿于怀的多线程同步与性能的平衡问题!!!

dronly 2009-10-21 08:00:53
加精
这是个我一直没解决的问题,首先让我们看一段程序:
//准备同步锁
bool Lock(){ return WaitForSingleObject(m_hMutex) == WAIT_OBJECT_0 }
bool UnLock() { return releaseMutex(m_hMutex) }

//插入一个描述客户端的类的对象
try{
if( Lock() ){
for ( int Ctr = 0 ; Ctr < m_ClientVector.size() ; Ctr++ ){
m_ClientVector.push_back(pNewClient); //pNewClient 是客户端连接上来后自动new的一个对象
}
UnLock();
}
}catch(Exception *e){

}

//发送一个数据
try{
if( Lock() ){
for ( int Ctr = 0 ; Ctr < m_ClientVector.size() ; Ctr++ ){
if(m_ClientVector[Ctr]->m_IP!=SrcIP) continue; //SrcIP是一个我要发数据的目的IP,由函数形参提供
m_ClientVector[Ctr]->Send(TmpBuf,Len); //TmpBuf与Len是一个外部构造的有数据的byte数组和他的长度
break;
}
UnLock();
}
}catch(Exception *e){

}

以上的代码是一个多线程网络控制系统下的一部分,软件可以是服务器,也可以是多个客户端的集合,这个代码实现了什么功能不是重点,重点是:
1. m_ClientVector 是现成共享的,无论主线程还是子线程,访问起来都要加锁。
2. m_ClientVector 用于描述TCP/IP网络连接上来的客户端,m_ClientVector 就是这些描述的对象的队列或链表
3. 假设 ClientVector[Index] 为一个描述客户端的数据,那么Send(),Rev(),AddData(), DelData()等等跟网络收发操作有关系的地方我都必须用 ClientVector[Index]->Send()这样的形式来做。
4. 根据第3点为基础,我们可以看出,其实m_ClientVector[Index]跟m_ClientVector[Index+1]和m_ClientVector[Index-1]是没有一点关系的,是互不相交的,其实我们m_ClientVector[Index]->Send(),m_ClientVector[Index+1]->Send(),和m_ClientVector[Index-1]->Send()也是互不影响的,因此是可以多线程的一起执行的,但根据上面的代码,因为m_ClientVector这整个队列本来就是线程共享的,而且不知道什么时候我们会进去增加或者减少这个队列,因此就算我们紧紧是想访问m_ClientVector[Index],但我们却要把整个m_ClientVector锁起来。

那么大家应该也看出来了,第四点是我们的主要问题,为什么呢?看以下假设:
1. 我们有线程Thread[X] ,X为30。这些线程能够类似windows那样接收到“消息”,然后自动的resume,根据“消息”跑不同的函数
2. 我们假设Thread[1],接受到“消息”启动起来了,要往IP1发送buf1数据。
3. 同时Thread[2]也接收到“消息”启动起来了,要往IP2发送部分2数据。
4. 然后根据最上面的代码,只有Thread[1]或者Thread[2]其中一个线程得到锁,进去发送了。
5. 但其实这个时候就算不锁,Thread[2]做的事情并不会影响Thread[1]做的事情,是可以同时并发的。
6. 但是因为我们不知道m_ClientVector他本身什么时候改变,因此我们必须要锁住整个队列,置使这个队列的操作都被锁住了

因此我们得到一个结论:
1. 现在这个m_ClientVector整个锁住再针对IP操作的方法是最安全的,但所有线程在这个地方都不是并发的了,而是变成串行的了,性能是受影响的。
2. 如果我们不锁m_ClientVector,我们单独对m_ClientVector里面的[Index]锁其实是不安全的,但这个其实也是合理的,因为每个m_ClientVector[Index]之间的操作时可以并发同时运行的。但如果我们不锁,我们在搜索要发的数据的时候,突然有一个线程要插入一个新的ClientVector[Index+n],两线程同时操作一个队列又是错误的,必然在指令级别上产生无法解析的错误。

这个性能与安全性之间的矛盾就是我一直没办法解决的问题,我的理想是可以很安全的做到每个ClientVector[Index]同步操作。

曾经有网友跟我说过主动对象,说过可以针对每个ClientVector[Index]去用一个线程维护,那么这个时候我想起了网游的服务器,标称同时上千上网个用户同时在线的网友服务器,也用这种每个玩家一个线程的做法,那么这个系统要建立上千上万的线程,这样在线程切换上花掉的CPU时间比真正CPU处理数据的时间可能还多,这样的做法是得不偿失的。

但网游这个地方还是处理得很好的,因此我觉得我这个理想是可行的。

有时间可以参考我2007年提过的问题:
http://topic.csdn.net/u/20071210/16/2f8b474b-62ff-4339-a3df-963ae501fcb3.html

感谢各位高手,一起探讨的朋友我都会给分的。


...全文
4857 150 打赏 收藏 转发到动态 举报
写回复
用AI写文章
150 条回复
切换为时间正序
请发表友善的回复…
发表回复
littlegang 2012-08-24
  • 打赏
  • 举报
回复
以上描述 凭记忆写的,十多年了,可能写得不准确,建议看书核对

以上monitor操作,在某些极端情况下,效率可能会比较低,但是如果是写较少,读很多的情况下,整体效率应该是不错的

littlegang 2012-08-24
  • 打赏
  • 举报
回复
我来挖坟。
其实楼主的问题,在“操作系统原理”(我看的是老早复旦大学的一本教科书)里面已经讲得很透了,建立一个 Monitor过程就可以,由monitor来负责锁住所有的读者 与 写者 之间的矛盾, 形成1写多读的方式,大致过程如下:

写者 进入monitor,申请写, monitor 请求 semaphore(n)n = 允许的读者数量, 如成功,wait在写mutex上(用于写互斥以及读写互斥),mutex可用后则放行该写者, 否则 进入等待; 写者写完后, 通知monitor 释放 semaphore(n)

读者 进入monitor,申请写, monitor 请求semaphore(1),放行1个读者; 读者完成后,通知 monitor 释放 semaphore(n)。

对于lz的问题, 读写段 限定在 vector的遍历上即可。 读者线程获得 vector[i]后,就可以释放资源,后续对vector[i]可以另行安排 互斥变量,比如 v_mutex[i]
ASDF8778ASDF 2010-12-13
  • 打赏
  • 举报
回复
楼主,我遇到跟你一样的问题,纠结了两个礼拜,有种cpu不能模拟现实环境的感觉,cpu是一维的,现实环境是多维的。你的帖给我启发很大。我也发过一个帖子:http://topic.csdn.net/u/20101204/13/d34a0b86-9cf1-45e7-bad9-de3fca2d1e46.htm
woszsj 2010-11-07
  • 打赏
  • 举报
回复
每天回帖10分
bucherren 2010-08-18
  • 打赏
  • 举报
回复
记得windows核心编程有一段读写锁,现在win2008server有InitializeSRWLock函数了
feizhuangxuan 2010-08-08
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 fanbin23 的回复:]
这个问题不会困扰了两年吧……

这是个典型的粗细粒度锁问题。现有的STL因为是面向串行程序的,因此如果写并行程序,只能用粗粒度锁,也就是你说的锁住整个vector。如果想提高性能,只有自己动手修改push_back的算法,插入某个元素时,只锁住与之相关的node(应该就是插入点的那个node),同时在Send的之前也只锁住相应的node。如果想进一步提高性能,可以查一下pthread中读者/写……
[/Quote]

呵呵,循环队列,无锁机制
t86591648 2010-02-02
  • 打赏
  • 举报
回复
MARK
xwsn007 2010-01-21
  • 打赏
  • 举报
回复
MARK
lianger84 2009-10-30
  • 打赏
  • 举报
回复
在连接没有这么多的时候,也是这么实现的,一直在想有没有更好的实现方法,帮顶,学习
new_universe 2009-10-27
  • 打赏
  • 举报
回复
ClientVector[Ctr]->m_IP!=SrcIP) continue; //SrcIP是一个我要发数据的目的IP,由函数形参提供
{
m_ClientVector[Ctr]->LockMy();
m_ClientVector[Ctr]->Send(TmpBuf,Len); //TmpBuf与Len是一个外部构造的有数据的byte数组和他的长度
m_ClientVector[Ctr]->UnlockMy();
}
break;
}

{
//我使用完列表啦,减掉记数,你们想干嘛干嘛关我P事
haggard_hunan 2009-10-27
  • 打赏
  • 举报
回复
锁的损耗是很小的,关键是你的代码处理以及逻辑设计:
1:进锁时不要太多阻塞操作
2:io操作要考虑好
3:程序效率
4:程序设计时要多考虑好消息的单向性。
5:防止死锁
NYN 2009-10-27
  • 打赏
  • 举报
回复
我就是搞mt og的,我的解决办法是对每个线程分配2个buffer[send buffer / recv buffer],send和recv分开做,send取这些buffer的内容,recv把内容放到对应的buffer中去。
fqli1610 2009-10-27
  • 打赏
  • 举报
回复
真是好长,先记一下,再继续看
blueness883 2009-10-27
  • 打赏
  • 举报
回复
搞清楚锁的类型?锁的目标是谁?
这2点明白了,就好办了.
WinEggDrop 2009-10-26
  • 打赏
  • 举报
回复
代码逻辑上就有问题了。连数据发送都做了锁操作,如果发送阻塞,就算你做了发送超时机制,但每个发送锁上几秒的话,请求多的话,如果网络一出问题,很容易就是一个长时间的死锁。理论上都能发现的问题,实际中肯定就会出现这情况。

m_ClientVector的数据是多线程共享的,所以要锁下,但你做那些发送操作时,难道不可以将m_ClientVector要操作的那个Node(m_ClientVector[Index]复制一份,这样用复制出来的进行发送操作不行吗?这样锁操作只是锁上复制的那个代码,基本上就解决上面会出现死锁问题了(除非你的m_ClientVector中的数据大都连用memcpy()等复制都会阻塞一段时间吧)
dronly 2009-10-26
  • 打赏
  • 举报
回复
to blueness883:
很感谢你提供的资料,现在积累的技术点非常多,我都在研究,研究途中才发现我原创的以为非常完美的“线程消息中心”是多么狭隘的东西,再此感谢你提供的资料

to webwebweb:
IOCP对服务器连接控制的确可以并发,但对于我内部数据里面,因为是“通用”线程的关系,因此网络控制并发仅仅是一个部分,我这边还是比较倾向于多读锁的例子。
dronly 2009-10-26
  • 打赏
  • 举报
回复
首先,对我的姗姗来迟道歉,因为研究线程池跟读写锁的中途,发现了很多宝贵

的东西,无意中乐而忘返了好几天,以至于荒废了很多很多。

to xhmff9:
程序我基本上看完了,虽然里面有些问题,例如if(bIsWait = TRUE) ,
for(;;)
{
if(nDo == 0)
break;

Sleep(50);
}
这个会导致Lock3死锁的,因为没法出来。

不过还是能看出来你的做法,Lock1负责bIsOpVector同步读写,Lock3负责nDo的同步读写,Lock2负责修改vector的同步,Lock3同时也负责访问上的同步,两个锁的顺序在读和写的时候调过来使用,使得读写总是互斥,这个是强读写锁的想法,这个我还是认同了。

考虑问题别整天想什么粒度呀、效率、XX的什么华而不实的东西,你连最基本的问题都还没解决就想这些东西那有什么用,写程序其实就像老百姓买菜煮饭过日子,朴实得不能再朴实,简单得不能再简单,非搞些什么听起来大而飘渺的东西那是JAVA的想法,不是我们C/C++饭们应有的做事方针
==================================================
我同意你的"写程序其实就像老百姓买菜煮饭过日子,朴实得不能再朴实,简单得不能再简单"这个说法,我们做事情不能把简单的事情复杂化,我也没想"你要修改列表你不等待别人用完你再改你还能怎么样"这个问题我从来没有执著过这些问题去要解决,我是把我真实的编程体现,在工作之余拿出来探讨,这个是一个技术爱好者对技术的执著,仅此而已。

to glyc:
这些资料我都在研究,在研究完相关的东西只有,我会有所总结,然后公开我的研究结果。

to jeffchen:
引用<<论语>> "有朋自远方来,不亦乐乎"

to kyee:
很感谢你提供的资料,这个代码很有原创的味道,而且很实际,我正在研究是否能加入我现在的实际项目中。
beyonld 2009-10-26
  • 打赏
  • 举报
回复
UP
sjzxyg 2009-10-26
  • 打赏
  • 举报
回复
锁对象细化,不要什么都加大锁,同时加上读写锁
joimson 2009-10-26
  • 打赏
  • 举报
回复
学习了
加载更多回复(124)

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧