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

一直耿耿于怀的多线程同步与性能的平衡问题!!!
这是个我一直没解决的问题,首先让我们看一段程序:
//准备同步锁
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

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


 

------解决方案--------------------
bool Lock(){ return WaitForSingleObject(m_hMutex) == WAIT_OBJECT_0 } 
bool UnLock() { return releaseMutex(m_hMutex) } 

如果只是控制共享数据的读写,一般不要用内核对象,可以使用关键代码段(CRITICAL_SECTION),前者是内核级同步,后者是用方式同步,后者效率远高于前者
------解决方案--------------------
这么大段的同步锁,很累的...

数据访问时发生冲突前进行加锁,在访问数据后立即解锁.

像你现在这么干,如果遇上网络丢包发生重传的话,线程一准卡死.


------解决方案--------------------
这个问题不会困扰了两年吧……

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

现在也有一些商业/开源的并行数据结构库(Java 6开始好像应该就有concurrent库了吧?)已经实现了队列插入/搜索的并行算法,也可以借用。
------解决方案--------------------