事件迴圈的概念是如何演變的
在虛擬碼中的 Eventloop
事件迴圈是一個等待事件然後對這些事件作出反應的迴圈
while true:
wait for something to happen
react to whatever happened
沒有事件迴圈的單執行緒 HTTP 伺服器的示例
while true:
socket = wait for the next TCP connection
read the HTTP request headers from (socket)
file_contents = fetch the requested file from disk
write the HTTP response headers to (socket)
write the (file_contents) to (socket)
close(socket)
這是一個簡單形式的 HTTP 伺服器,它是一個單執行緒但沒有事件迴圈。這裡的問題是它等待每個請求完成後再開始處理下一個請求。如果需要一段時間來讀取 HTTP 請求標頭或從磁碟獲取檔案,我們應該能夠在等待完成時開始處理下一個請求。
最常見的解決方案是使程式多執行緒化。
沒有事件迴圈的多執行緒 HTTP 伺服器的示例
function handle_connection(socket):
read the HTTP request headers from (socket)
file_contents = fetch the requested file from disk
write the HTTP response headers to (socket)
write the (file_contents) to (socket)
close(socket)
while true:
socket = wait for the next TCP connection
spawn a new thread doing handle_connection(socket)
現在我們已經使我們的小 HTTP 伺服器多執行緒。這樣,我們可以立即轉到下一個請求,因為當前請求正在後臺執行緒中執行。包括 Apache 在內的許多伺服器都使用這種方法。
但它並不完美。一個限制是你只能產生這麼多執行緒。對於具有大量連線的工作負載,但每個連線只需要偶爾注意一次,多執行緒模型將無法很好地執行。這些情況的解決方案是使用事件迴圈:
具有事件迴圈的 HTTP 伺服器的示例
while true:
event = wait for the next event to happen
if (event.type == NEW_TCP_CONNECTION):
conn = new Connection
conn.socket = event.socket
start reading HTTP request headers from (conn.socket) with userdata = (conn)
else if (event.type == FINISHED_READING_FROM_SOCKET):
conn = event.userdata
start fetching the requested file from disk with userdata = (conn)
else if (event.type == FINISHED_READING_FROM_DISK):
conn = event.userdata
conn.file_contents = the data we fetched from disk
conn.current_state = "writing headers"
start writing the HTTP response headers to (conn.socket) with userdata = (conn)
else if (event.type == FINISHED_WRITING_TO_SOCKET):
conn = event.userdata
if (conn.current_state == "writing headers"):
conn.current_state = "writing file contents"
start writing (conn.file_contents) to (conn.socket) with userdata = (conn)
else if (conn.current_state == "writing file contents"):
close(conn.socket)
希望這個虛擬碼是可理解的。這是正在發生的事情:我們等待事情發生。無論何時建立新連線或現有連線需要我們注意,我們都會處理它,然後再回去等待。這樣,當有很多連線時我們表現很好,每個連線都很少需要注意。
在 Linux 上執行的實際應用程式(非虛擬碼)中,將通過呼叫 poll()
或 epoll()系統呼叫來實現等待下一個事件發生部分。通過在非阻塞模式下呼叫 recv()
或 send()系統呼叫來實現“開始讀取/寫入套接字”部分。
參考:
[1]。 “事件迴圈如何運作?” [線上]。可用: https : //www.quora.com/How-does-an-event-loop-work