使用异步socket的时候需要注意memory spike(转)
这个是我在网上看的一篇文章,原文地址为:http://morganchengmo.spaces.live.com/blog/cns!9950CE918939932E!3022.entry
在.net 中,内存是被系统托管的,程序员无需关心内存泄露问题,但是,在异步socket的时候,这个却是不大靠得住的,虽然不会出现memory
leak,但会出现功能类似的memory spike。
按照KB947862(http://support.microsoft.com/kb/947862)的说法,使用Socket和NetworkStream的异步API是靠不住的,这是一个很严重的问题!
我们来看看Stream异步读的接口什么样子
public virtual IAsyncResult BeginRead(
byte[] buffer, int offset, int
count, AsyncCallback callback, Object state )我们使用这个API的时候,肯定要先分配一个byte
array作为buffer参数,调用BeginRead之后,理论上当有数据出现在Stream上或者timeout的时候,callback应该被回调,但是,按照KB的说法,如果对方停止了I/O,可能永远也不会回调这个callback。作为参数buffer传递进去的byte
array,.NET为了防止其被Garbage
Collected,用一个内部的System.Threading.OverlappedData实例引用它,对每个一部调用都会建立一个OverlappedData实例,在异步操作结束的时候释放,现在,因为callback可能永远不会被调用,异步操作也就永远不会结束,对应的OverlappedData也就永远不会被释放,同样的,我们分配的byte
array也就永远不会被GC,这段byte array也就等于leak了。最近我就发现我的一个程序使用内存特别多,用Windbg一看,内存中有不正常数量的OverlappedData和byte
array,原因就是使用了NetworkStream.BeginRead。这篇KB上提供的解决方法是这样:
The .NET library that allows Asynchronous IO with dot net sockets
(Socket.BeginSend / Socket.BeginReceive / NetworkStream.BeginRead /
NetworkStream.BeginWrite) must have an upper bound on the amount of buffers
outstanding (either send or receive) with their asynchronous IO.
The network application should have an upper bound on the number of
*outstanding* asynchronous IO that it posts.
这样的解决方法说了和没说一样,到底这个"Upper
Bound"是多少?有没有具体数字,难道要每个使用这个API的程序员来一个数字一个数字来试吗?我觉得根本就没有这样的"Upper
Bound"能够避免这种问题。
作者认为:
MSDN中提到“memory spike”,实际上就是"memory
leak",我们看是怎么回事:
使用这样的异步流程,我建立一个Socket,我在这个Socket上调用BeginReceive,同时给一个回调函数(这个回调函数肯定是要调用这个Socket的EndReceive),在给定时间(timeout)内,如果回调函数被调用了,在这个回调函数中调用了EndReceive,这样一个必须成对的Begin/End操作就结束了,这是正常情况,在不正常情况下,回调函数没有在timeout下被调用,我们的程序不能就这么永远干等着呵,所以我应该abort这个一部操作,能做的选择就是Close这个Socket,理论上,.NET这时候也应该把所有相关资源都释放了,但是如同我原文中所说,它未必这样做。.NET地实现存在问题,在某些情况下,虽然在应用层我们能做释放资源的事情都做了,但是.NET却不认为事情结束了,所以依然保留着对相关资源的reference,比如对我在BeginReceive时给它的byte
array,我的应用程序已经不referece这个byte
array了,也不reference那个Socket了,也把Socket给Close了,也就是说,.NET理应释放这些资源了,但是.NET还是保留着对byte
array的reference,这样GC就没法回收它。
The network application should
have an upper bound on the number of *outstanding*
asynchronous IO that it
posts.
这句话的意思是,程序不应维护过多的异步I/O,一个程序完全可能有多个对外的异步I/O,不给出这个上限,这句话就是没有任何指导意义的,这就只是.NET的一个bug。
但也有读者认同微软的说法:
The network application should have an upper bound on the number of
*outstanding* asynchronous IO that it
posts.
这话说得不是没有道理,你仔细琢磨一下。如果严格的说起来对于一个给定的连接,如果没有bug,是不是1就是上限呢?如果你期待一个回应,而在得到它之前是否应该在调用BeginReceive(如果你的协议不存在问题的话)?
所以要想用异步,对于一个给定的连接你必须维护一个清晰的状态机。否则就会乱掉,而造成你看到的memory
spike。
memory spike 不等于memory
leak。如果你端掉该连接,释放次变量,.net还是会自动释放内存的。这属于调用者的问题,而不是microsoft的问题。之所以c++没有至一问题,是因为你必须手动释放内存,所以调用者理所当然的认为这是他自己的责任。
总之,在使用异步socket的时候,memory spike是一个需要关注的地方,在这种系统无法保证能有效释放的资源,还是需要自己手动控制的。