我在寫入IO緩衝區和套接字之間傳輸數據的線程時出現問題。我沒有任何問題讓它運行,但不是我想要的方式。下面是代碼草圖:沒有輪詢的基於選擇的套接字循環
s = socket(...) # some connection
in_buffer = b'' # consumed by other thread
out_buffer = b'' # produced by other thread
while True:
(r, w, x) = select([s], [s], [s])
if r:
in_buffer += s.recv(RECV_LIMIT)
if w:
sent = s.send(out_buffer)
out_buffer = out_buffer[sent:]
if x:
break
問題在於它閒置時會佔用一個完整的CPU。原因是套接字大部分時間都是可寫的,特別是閒置時。立即select()
回報,什麼也不做,再次呼籲select()
,什麼都不做等有一個簡單的修復,不檢查一個可寫的插座,當你沒有什麼寫:
... # dito
while True:
if out_buffer:
(r, w, x) = select([s], [s], [s])
else:
(r, w, x) = select([s], [], [s])
... # dito
這工作,但它有一個不同的問題:空閒時,這個塊無限制地在select()
上。如果我添加一些東西到輸出緩衝區,我需要以某種方式喚醒來自accept()
調用的線程,但是如何?爲了記錄,我當前的解決方法稍微改變了評估:
while True:
(r, w, x) = select([s], [s], [s])
if x:
break
elif r:
in_buffer += s.recv(RECV_LIMIT)
elif w:
if out_buffer:
sent = s.send(out_buffer)
out_buffer = out_buffer[sent:]
else:
sleep(0.001)
總之,當確實沒有什麼可做的時候,插入延遲。毫秒足以甚至不會消耗1%的CPU。類似的方法是使用select()
調用的超時,然後重新檢查輸出數據的存在。儘管如此,這兩種解決方案都不是很好,因爲兩者都有效地歸結爲投票和投票。那麼,我該如何編寫一個IO線程,像這樣可移植並且無需輪詢?
注意:一種方法是添加另一個文件描述符,在該文件描述符上創建人造流量以便從阻塞select()
調用中喚醒線程。在這裏,問題是select()
只能在插座上移植使用,而不是例如。管道。或者,在MS Windows上,我可以將一個win32事件與一個套接字的狀態更改和另一個事件關聯起來(請參閱WSAEventSelect),但我不想將此代碼寫入非移植式WinSock API的頂層。
發送字節上的一個套接字喚醒線程技巧工程在我的代碼罰款。如果您使用TCP或UDP套接字而不是管道,則也可以在Windows下執行此操作。 – 2013-05-07 21:26:24
你建議創建一對loopback套接字來喚醒線程。是的,它肯定有效,毫無疑問。我很確定它涉及將數據從一個進程編組到內核的IP棧並回到相同的進程,這可能甚至比我目前的黑客還要慢。 – 2013-05-07 21:58:06
我只能說我在我的(相當性能苛刻的,基於C++的)程序中使用了這個技術,我從來沒有注意到任何放緩因爲它。我認爲通過環回設備發送數據的數據路徑已經很好地優化了,並且在任何情況下,您只需要在每次喚醒時發送一個字節。鑑於你的代碼無論如何都在Python解釋器中運行,我不認爲你會注意到任何放緩。 (但請務必關閉Nagle的算法) – 2013-05-07 22:47:40