(Go語言)Go的網絡輪詢及IO機制

原創文章,轉載請註明出處:服務器非業餘研究-sunface

簡介

這篇介紹了Go的運行時系統——網絡I/O部分。


阻塞

Go語言中,所有的I/O都是阻塞的,因此我們在寫Go系統的時候要秉持一個思想:不要寫阻塞的interface和代碼,然後通過goroutines和channels來處理併發,而不是用回調和futures。其中一個例子是“net/http"包中的http服務器,無論何時當http服務器接收一個連接,它都會創建一個新的goroutine處理來自這個連接的所有請求,這樣我們就能寫出很清晰的代碼:先做什麼,然後做什麼。然而,不幸的是,操作系統提供的阻塞式I/O並不適合構建我們自己的阻塞式I/O接口(interface)。

在我之前有關Go運行時的文章中,其中一篇介紹了Go調度器如何處理系統調用。爲了處理一個阻塞式的系統調用,我們需要一個操作系統線程,因此如果要在OS的I/O層之上構建我們自己的阻塞式I/O層,則需要爲每一個goroutine客戶端連接產生一個新的線程,因爲這些連接執行系統調用的時候會阻塞。當你擁有10000個客戶端線程時構建我們自己的阻塞式I/O層會變得異常昂貴,因爲這些客戶端都會阻塞在自己的系統調用上等待I/O操作成功。

這裏Go通過使用OS提供的異步接口(epoll等)來解決上述這個問題,但是會阻塞那些正在執行I/O的goroutines。


網絡輪詢(Netpoller)
將異步式I/O轉爲阻塞式I/O是通過網絡輪詢(netpoller)這個部分來完成的,netpoller在自己的線程中,接收那些準備進行網絡I/O的goroutines發來的消息,netpoller使用操作接口提供的接口來提供對網絡sockets的輪詢。在linux系統中,這個接口是epoll,在BSDs和Darwin中是kqueue,在Windows中是IoCompletionPort。這些接口的共同之處是它們爲用戶空間輪詢網絡IO狀態提供了非常高效的方法。

無論何時在Go程序中打開或者接受一個連接,該連接背後的文件描述符都被設置爲非阻塞模式。如果在文件描述符還沒準備好的時候,試着使用該連接進行I/O,就會返回一個無法進行I/O的錯誤。當一個goroutine嘗試對一個連接進行讀或者寫的時候,如果沒有收到上述報錯,則執行這次操作,否則調用netpoller,當可以執行I/O的時候讓netpoller通知goroutine,隨之goroutine就被從正在運行的線程上調度出去等待通知。

當操作系統通知netpoller可以在一個文件描述符上運行I/O後,它就會檢查內部的數據結構,看看是否有goroutines正阻塞在那個文件上,如果有則通知這些goroutines,這些goroutines隨後會重新嘗試在之前導致它阻塞的I/O操作。

如果你覺得之前的介紹是在使用舊式的Unix系統中的select和poll方法,那恭喜你,基本猜對了。但是Go的netpoller查詢的是能被調度的goroutine而不是那些函數指針、包含了各種狀態變量的struct等,這樣你就不用管理這些狀態,也不用重新檢查函數指針等,這些都是你在傳統Unix網絡I/O需要操心的問題,Go中不需要。

發佈了132 篇原創文章 · 獲贊 38 · 訪問量 42萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章