使用非同步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是一個需要關注的地方,在這種系統無法保證能有效釋放的資源,還是需要自己手動控制的。
這個是我在網上看的一篇文章,原文地址為: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是一個需要關注的地方,在這種系統無法保證能有效釋放的資源,還是需要自己手動控制的。