线程情况: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就麻烦了。问题就是这里该如何处理最恰当?
数据结构:一个客户连接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就麻烦了。问题就是这里该如何处理最恰当?
C删除socket_context并退出临界,A/B正阻塞在临界,C退出然后A或B进入读写不出错了吗?
我用COM里的引用计数器来解决,虽然不是很完善
context的CPU CACHE同步刷新的损失。
导致多个CPU的CACHE同步刷新。
sunhuiNO1(2B) 还在吗?能把你的解决方法分享一下吗?提示一下也行啊
你想得太简单了,一般完成端口用2个或2个以上的工作线程来做IO完成后的事,这才能体现完成端口的优势所在。假设对1个socket句柄发出2个WSARecv请求,这2个IO的完成队列就有可能分别被2个线程GetQueuedCompletionStatus()。如果你对所有的context操作都用一个临界量控制,那效率显得也太低了。
同样,C把socket关掉时,如果a/b线程正阻塞在GetQueuedCompletionStatus(),那么a/b是可以得到消息,如果a/b线程已经进入完成处理阶段
2、把io_context和socket_context合并成一个
3、C线程在清除的时候,直接closesocket,其他的工作不要做。
这样的话,并发情况不是很严重的时候,运行问题不大
a、b同时处理完成的情况,难道c就不能通过context知道么,它直接去触发清除就行(它不管清除,这样稳当吧)
临界量只有你自己都把握不准的时候才用来线程同步,如果你肯定各线程互相不会干扰,那还要它干吗?
1、CreateIoCompletionPort时指定的线程和完成处理工作线程是两回事!
当然,工作线程也可以指定为1个,但是那就失去完成端口的意义了。
2、我的应用要应付大量的tcp连接和数据传输;
2、不好意思,没看懂你要说什么
呵呵,我还是建议先把程序做的稳定点,然后再探讨高效率的问题。
NumberOfConcurrentThreads指的是等待IO事件的线程队列数量,不是程序执行中的工作线程的并发数量
我的系统要支持1000个tcp连接在线,服务器是2个CPU,同时还有数据库连接和操作,所以不得不考虑性能问题。
你遇到的问题我也遇到过。
(其实用完成端口或重叠IO都会遇到delete sock_context时需要同步的问题)我是用引用计数来解决的。
智能指针你知道吧?和它的解决方案差不多。
以下为我个人的想法
可以为每一个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
{忽略该次删除请求。
}
}
那你的程序没充分利用2个cpu的优势啊
你是说即使多cpu情况下,系统对int count的操作总是同步的吗?
那么win32 api InterlockedXXX()岂不是画蛇添足?
P4超线程所以显示4个CPU,一般连续运行1个月左右,从今年4月
到目前还没有什么问题出现,由于服务器程序是网络游戏使用的,
受到的攻击和压力决非普通MIS类的程序可以比的,偶认为设置一
个线程完全没必要,浪费CPU资源。
我不用引用计数也解决了这个问题,而且每次发送不用增加引用记数,减少了多线程操作同一个
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下同步手段中最高效的。虽然是会降低性能,但是是可以接受的,在多线程程序中,同步不能是谈虎色变。
CPU占用在7%~15%之间
垃圾回收机制具体是怎么回事?
关于对同一个socket发出>=2个WSASend/WSARecv确实是设计问题,我已经改进;
现在剩下socket_context删除和同步访问问题了。
++指令当然不是原子操作。有可能被别的线程中断。 偶本来想抛砖引玉的,没想到迎来你的板砖…),玉在谁那里呢?在sunhuiNO1(2B) 那里,因为他说了他知道~~~不过我看了
elssann(臭屁虫和他的开心果) 说是 垃圾回收机制 ,名字听说过,还不知道怎么实现~~~~我自己是这么来做的:工作者线程和检测线程之间没有同步,但是工作者线程之间是同步的。
context资源中的时间变量timecount,检测线程每隔5秒遍历所有资源,资源的timecount+=5;
if(timecount>120)//空闲时间2分钟。
PostQueuedCompletionStatus();//向完成端口发送一个断开该连接的标志
(这种情况下即使我读到的是垃圾数据,也没关系,比如当我刚刚把timecount读出的时候,工作者线程将timecount=0)当工作者线程首先收到检测线程的结束请求。响应该请求,在同步机制的保护下(比如CRITICAL_SECTION)检测
if(timecount>120)
delete context;在这里进行真正的删除。
也就是说,当检测线程偶然发出错误的删除请求,但是该请求并没有被真正的响应。
如果是原子操作,MS还要设计InterlockXXX这些函数干虾米?
这样做过的人比如sunhuiNO1(2B) 我一说是垃圾回收机制他就知道。
sunhuiNO1(2B) 为什么说半句就跑了呢?
elssann(臭屁虫和他的开心果) ( ) 采用类似垃圾回收机制一样,,,做一个FIFO
----------------------------------------------------------------------------------------
如果我的理解没有错误的话,用一个链表可以实现类似的功能,只是我没把这个抽象到垃圾回收的机制上来。只是这个好像跟同步不同步没什么关系吧?
呵呵,我也不知道什么是垃圾回收机制,
但驱动中常使用的后备链表(旁视列表)应该是他们所说的那个东东。
-------------------------------------------
你说的LOOKASIDE链表是用来实现内存池的,和这个木有关系
垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。
垃圾收集的算法分析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算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行
2。多个线程间对context的操作访问时候,不加锁直接访问,而且连接断开后:不释放context空间,只设置标志。如果这时候有其他线程返问,先判断下标志再使用。就算连接断开的时候还没来得及设定标志也没关系,因为此时SOCKET操作肯定会失败,你一样也能知道连接已经出错了。
3。每次新建立连接的context自动加到一个FIFO的队列尾。
4。应该有一个垃圾回收器,怎么识别哪些context该回收呢? 应该回收的数 N = FIFO队列数 - 当前连接数 。 从FIFO队列出队前N个就是要回收的垃圾节点。
不知道想的是否正确,请大侠批评指正