线程情况:A、B是IO完成处理线程,C是连接检查线程;
数据结构:一个客户连接socket对应一个socket_context,一次IO对应一个io_context;
有以下情景:
1、为了接收客户端的一个请求,必须发出2次WSARecv(),这两次IO请求完成后必须将收到的数据合并到一起保存到socket_context里;这2次的完成处理可能分别在A、B两个线程里执行,所以A、B线程在读写同一个socket_context时,必须处理好同步关系;这里我用InterlockedXXX()函数改变socket_context中的标记量实现。
2、当某个连接很久不发送某一种特定的数据时,C线程就会将该连接关闭,关闭时当然最好马上把socket_context清理掉;但是,C线程在做这个操作时,如果A/B正在读写socket_context就麻烦了。问题就是这里该如何处理最恰当?

解决方案 »

  1.   

    A,B,C中对socket_context操作时候,均加琐.解锁不行吗?
      

  2.   

    读写当然可以用CRITICAL_SECTION,删除就不行了。
    C删除socket_context并退出临界,A/B正阻塞在临界,C退出然后A或B进入读写不出错了吗?
    我用COM里的引用计数器来解决,虽然不是很完善
      

  3.   

    我不用引用计数也解决了这个问题,而且每次发送不用增加引用记数,减少了多线程操作同一个
    context的CPU CACHE同步刷新的损失。
      

  4.   

    请问 sunhuiNO1(2B) 是怎么解决这个问题的?另外我的程序使用了大量的InterlockedXXX等函数,不知道会不会降低性能?
      

  5.   

    InterlockedXXX会影响性能,尤其是多CPU环境下,多个InterlockedXXX函数可能操作同一个变量会
    导致多个CPU的CACHE同步刷新。
      

  6.   

    能告诉我你的解决方法吗?非常感谢,我的MSN/邮箱[email protected]
      

  7.   

    看看《Windows核心编程》里面的多线程同步,里面有读写控制方面的东西
      

  8.   

    to BigFanTian(大梵天):读写同步就简单了,这里是删除!
     sunhuiNO1(2B) 还在吗?能把你的解决方法分享一下吗?提示一下也行啊
      

  9.   

    设计不合理,应该让一个线程来操作这个context,因为你的需求实际上是顺序化的,而非并发发生,另外c线程杀context,也不对,最好的办法就是简单把socket关掉,a和b自然得到消息
      

  10.   

    to  tangrh(阿唐) :
        你想得太简单了,一般完成端口用2个或2个以上的工作线程来做IO完成后的事,这才能体现完成端口的优势所在。假设对1个socket句柄发出2个WSARecv请求,这2个IO的完成队列就有可能分别被2个线程GetQueuedCompletionStatus()。如果你对所有的context操作都用一个临界量控制,那效率显得也太低了。
        同样,C把socket关掉时,如果a/b线程正阻塞在GetQueuedCompletionStatus(),那么a/b是可以得到消息,如果a/b线程已经进入完成处理阶段
      

  11.   

    1、CreateIoCompletionPort时指定只有一个线程在等待
    2、把io_context和socket_context合并成一个
    3、C线程在清除的时候,直接closesocket,其他的工作不要做。
    这样的话,并发情况不是很严重的时候,运行问题不大
      

  12.   

    呵呵,我不用临界量,谁用它啊,对同一个套接字而言,你发出两个wsarecv,如果同时完成,那你两个线程....反正我的线程都是无状态的,各context收发互不影响,实际上我认为,对同一个套接字,只有同时收发的需求,绝对不会有同时收收的需求,如果真那样,那包本来好不容易被下一层组完整了,结果又被你的收收线程分开了,还玩什么?
    a、b同时处理完成的情况,难道c就不能通过context知道么,它直接去触发清除就行(它不管清除,这样稳当吧)
    临界量只有你自己都把握不准的时候才用来线程同步,如果你肯定各线程互相不会干扰,那还要它干吗?
      

  13.   

    同时发出多个WSARecv可能不会,但多个WSASend还是有可能的,如果有一个工作线程发出一个WSASend,另外一个线程这个线程用于循环定时向客户端发送数据,那么就有可能出现多个WSASend了,我这样解决的:char SendBuf[1024]; int nSendDone; int nSendTotal;当前一个发送还未完成时,后面再请求发送时,则nSendTotal += len, CopyMemory(SendBuf+nSendDone, data, len),即追加,然后在发送完成处理里判断nSendDone<nSendTotal则继续发。其实这些处理当中还是可能出现冲突,例如:如果循环线程正在写nSendTotal变量:nSendTotal += len,完成处理线程却在读nSendTotal变量:nSendDone<nSendTotal,如果真的要谨慎的话就要用InterlockedXXX函数了。唉,要考虑的东西太多了。to barsteng(barsteng) :
       1、CreateIoCompletionPort时指定的线程和完成处理工作线程是两回事!
    当然,工作线程也可以指定为1个,但是那就失去完成端口的意义了。
       2、我的应用要应付大量的tcp连接和数据传输;
      

  14.   

    1、呵呵,你没有明白我说的意思,好好看看CreateIoCompletionPort中关于并发线程的解释。设置成1的情况下,让你的程序更简单——当然也更稳定。在你的程序不稳定的情况下,追求高效率有什么用?我的实际的服务程序,完成端口的,就是设定的1个等待线程,稳定运行1个月也没什么问题,7*24的客户端访问,每3-5秒一个客户端数据请求,(工业环境,三班倒,100个客户端),我记录的工作线程并发数量最高可以达到20个(这是因为并发的客户端请求太多)。而且,如果是一个CPU的机器,并发线程设置成多个的时候线程的频繁切换反而会导致效率降低——实际上,设置成多个并发线程,容易造成一个WSARecv操作可能每个线程都接收了一点。
    2、不好意思,没看懂你要说什么
    呵呵,我还是建议先把程序做的稳定点,然后再探讨高效率的问题。
      

  15.   

    补充一下:
    NumberOfConcurrentThreads指的是等待IO事件的线程队列数量,不是程序执行中的工作线程的并发数量
      

  16.   

    to barsteng(barsteng) :
    我的系统要支持1000个tcp连接在线,服务器是2个CPU,同时还有数据库连接和操作,所以不得不考虑性能问题。
      

  17.   

    to cctime () :
    你遇到的问题我也遇到过。
    (其实用完成端口或重叠IO都会遇到delete sock_context时需要同步的问题)我是用引用计数来解决的。
    智能指针你知道吧?和它的解决方案差不多。
      

  18.   

    sunhuiNO1(2B) 说不用引用计数器也能解决,我好奇想知道他是如何解决的
      

  19.   

    呵呵,如果是2个CPU,我真的建议你把并发线程数设置成1个,我的程序在2个CPU的服务器上运行的时候,出了问题,就是一个context会同时被2个线程处理,后来设置成1个就好了,并不影响性能,我的教训,供你借鉴
      

  20.   

    为什么可以不用InterlockedXXX:也就是说调用InterlockedXXX来同步是不必要的。
    以下为我个人的想法
    可以为每一个context设定一个整数int count
    当你分配该context资源的时候,设定count=1;表明该context始终有用。
    任意线程欲处理该context的时候,它应该是这么处理的
    if(count>0)
    {
      ++count;
      使用中:
      --count;
    }
    else
    {该资源已经不存在了;
    }
    也就是说当context被利用的时候,它怎么都是>1的。当context空闲的时候,context==1;
    当检测线程想清理该context的时候,应该是这样的:
    if(想删除context)

      if(count==1)
       {count=0;
       进行清理工作。}
       else
      {忽略该次删除请求。
      }
    }
      

  21.   

    to barsteng(barsteng) 
    那你的程序没充分利用2个cpu的优势啊
      

  22.   

    to redchina(风清云淡) :
      你是说即使多cpu情况下,系统对int count的操作总是同步的吗?
    那么win32 api InterlockedXXX()岂不是画蛇添足?
      

  23.   

    我的程序在2个CPU的至强P4上,完成端口开了8个工作线程(4*2)
    P4超线程所以显示4个CPU,一般连续运行1个月左右,从今年4月
    到目前还没有什么问题出现,由于服务器程序是网络游戏使用的,
    受到的攻击和压力决非普通MIS类的程序可以比的,偶认为设置一
    个线程完全没必要,浪费CPU资源。
      

  24.   

    回复人: sunhuiNO1(2B) ( ) 信誉:100  2004-11-25 17:34:20  得分: 0  
     
     
       
    我不用引用计数也解决了这个问题,而且每次发送不用增加引用记数,减少了多线程操作同一个
    context的CPU CACHE同步刷新的损失。-------------------------------------------------------------------------------------
    不用引用计数也可以解决,采用类似垃圾回收机制一样,,,做一个FIFO,  不知道你是不是这样做的。如果一个人的程序在同一个SOCKET有1个以上的WSASEND或者WSARECV,那么就是程序设计得有问题了,这里涉及到一个问题:比如你的两个WSASEND各要发送一个1K的数据,当A线程发送的时候,只发出去512字节,然后在GetQueueCompletionPortStatus返回后,你检查dwNumberBytes的时候,发现还没发完,对WSABUF缓冲区进行偏移的时候,这时候B线程开始发了,他也只发出去512字节,然后在GetQueueCompletionPortStatus返回后,他也偏移缓冲区后再次发送,这样就会出现TCP包逻辑上乱序,可能客户端收到的数据是: A线程的512字节 B线程发送的512字节,然后A线程的512字节 B线程的512字节。和你期望中的 收到数据: A线程发送的1K,B线程发送的1K完全不同。 同样WSARECV也存在这个问题。
     
    另外,InterlockXXX系列函数是WINDOWS下同步手段中最高效的。虽然是会降低性能,但是是可以接受的,在多线程程序中,同步不能是谈虎色变。
      

  25.   

    恩,我是采用垃圾回收机制来处理这个问题的,目前效率还不错,在1200并发连接的时候
    CPU占用在7%~15%之间
      

  26.   

    sunhuiNO1(2B) 来了就多说几句嘛,please
    垃圾回收机制具体是怎么回事?
    关于对同一个socket发出>=2个WSASend/WSARecv确实是设计问题,我已经改进;
    现在剩下socket_context删除和同步访问问题了。
      

  27.   

    to cctime()
    ++指令当然不是原子操作。有可能被别的线程中断。 偶本来想抛砖引玉的,没想到迎来你的板砖…),玉在谁那里呢?在sunhuiNO1(2B) 那里,因为他说了他知道~~~不过我看了
    elssann(臭屁虫和他的开心果) 说是 垃圾回收机制 ,名字听说过,还不知道怎么实现~~~~我自己是这么来做的:工作者线程和检测线程之间没有同步,但是工作者线程之间是同步的。
    context资源中的时间变量timecount,检测线程每隔5秒遍历所有资源,资源的timecount+=5;
    if(timecount>120)//空闲时间2分钟。
    PostQueuedCompletionStatus();//向完成端口发送一个断开该连接的标志
    (这种情况下即使我读到的是垃圾数据,也没关系,比如当我刚刚把timecount读出的时候,工作者线程将timecount=0)当工作者线程首先收到检测线程的结束请求。响应该请求,在同步机制的保护下(比如CRITICAL_SECTION)检测
    if(timecount>120)
    delete context;在这里进行真正的删除。
    也就是说,当检测线程偶然发出错误的删除请求,但是该请求并没有被真正的响应。
      

  28.   

    谁说++指令是原子操作啊, ,,
    如果是原子操作,MS还要设计InterlockXXX这些函数干虾米?
      

  29.   

    elssann(臭屁虫和他的开心果) ,那你说说你怎么搞得?思路?垃圾回收,总有个具体思路吧?
      

  30.   

    回复人: redchina(风清云淡) ( ) 信誉:98 :
    这样做过的人比如sunhuiNO1(2B) 我一说是垃圾回收机制他就知道。
      

  31.   

    垃圾回收机制和计数器到底不同在哪里啊?
    sunhuiNO1(2B) 为什么说半句就跑了呢?
      

  32.   

    quote:
    elssann(臭屁虫和他的开心果) ( ) 采用类似垃圾回收机制一样,,,做一个FIFO
    ----------------------------------------------------------------------------------------
    如果我的理解没有错误的话,用一个链表可以实现类似的功能,只是我没把这个抽象到垃圾回收的机制上来。只是这个好像跟同步不同步没什么关系吧?
      

  33.   

    回复人: stonex_2000(三棱镜) ( ) 信誉:101  2004-12-01 22:09:00  得分: 0  
     
     
       呵呵,我也不知道什么是垃圾回收机制,
    但驱动中常使用的后备链表(旁视列表)应该是他们所说的那个东东。
      
     
    -------------------------------------------
    你说的LOOKASIDE链表是用来实现内存池的,和这个木有关系
      

  34.   

    谁给个完整的服务器和客户端的例子,有分感谢,[email protected]
      

  35.   

    垃圾回收机制这个名词应该比较容易找得到解释,如今在火暴的.NET 和 JAVA中的都是很流行的名词,我来照抄一段吧:
    垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。
    垃圾收集的算法分析Java语言规范没有明确地说明Jvm使用哪种垃圾回收算法,但是任何一种垃圾收集算法一般要做2件基本的事情:(1)发现无用信息对象;
    (2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。大多数垃圾回收算法使用了根集(root set)这个概念;所谓根集就是正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量),程序可以使用引用变量访问对象的属性和调用对象的方法。垃圾收集首选需要确定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能作为垃圾被回收,这也包括从根集间接可达的对象。而根集通过任意路径不可达的对象符合垃圾收集的条件,应该被回收。下面介绍几个常用的算法。1、引用计数法(reference counting collector)引用计数法是唯一没有使用根集的垃圾回收得法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1。当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,必须实时运行。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量 ,计数器加1,而每次现有对象出了作用域生,计数器减1。2、tracing算法(tracing collector)tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除(-and-sweep)垃圾收集器。3、compacting算法(compacting collector)为了解决堆碎片问题,基于tracing的垃圾回收吸收了compacting算法的思想,在清除的过程中,算法将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些引用 在新的位置能识别原来 的对象。在基于compacting算法的收集器的实现中,一般增加句柄和句柄表。4、coping算法(coping collector)该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于coping算法的垃圾收集就从根集中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行
      

  36.   

    我想垃圾回收解决方法是不是这样的: ??  1。首先从设计上避免同时WSARECV和WSASEND
      2。多个线程间对context的操作访问时候,不加锁直接访问,而且连接断开后:不释放context空间,只设置标志。如果这时候有其他线程返问,先判断下标志再使用。就算连接断开的时候还没来得及设定标志也没关系,因为此时SOCKET操作肯定会失败,你一样也能知道连接已经出错了。
      3。每次新建立连接的context自动加到一个FIFO的队列尾。
      4。应该有一个垃圾回收器,怎么识别哪些context该回收呢? 应该回收的数 N = FIFO队列数 - 当前连接数  。 从FIFO队列出队前N个就是要回收的垃圾节点。
      
     不知道想的是否正确,请大侠批评指正