回覆列表
  • 1 # Go語言中文網

    Go有超高併發能力,它如何解決IO等待問題的?回答此問題前,我們需要先普及一下 IO 的相關知識點。

    01IO 基本概念Linux 的核心將所有外部裝置都可以看做一個檔案來操作(Unix 的設計原則,一切皆檔案)。那麼我們對外部裝置的操作都可以看做對檔案進行操作。我們對一個檔案的讀寫,都透過呼叫核心提供的系統呼叫;核心給我們返回一個 file descriptor(fd,檔案描述符)。對一個 socket 的讀寫也會有相應的描述符,稱為socketfd(socket 描述符)。描述符就是一個數字(可以理解為一個索引),指向核心中一個結構體(檔案路徑,資料區,等一些屬性)。應用程式對檔案的讀寫就透過對描述符的讀寫完成。一個基本的 IO,它會涉及到兩個系統物件,一個是呼叫這個 IO 的程序物件,另一個就是系統核心(kernel)。一般來說,伺服器端的 I/O 主要有兩種情況:一是來自網路的 I/O;二是對檔案(裝置)的I/O。02常見的 IO 模型首先一個 IO 操作其實分成了兩個步驟:發起 IO 請求(等待網路資料到達網絡卡並讀取到核心緩衝區,資料準備好)和實際的 IO 操作(從核心緩衝區複製資料到程序空間)。阻塞和非阻塞阻塞 IO 和非阻塞 IO 的區別在於第一步:發起 IO 請求是否會被阻塞。如果阻塞直到完成,那麼就是傳統的阻塞 IO,如果不阻塞,那麼就是非阻塞 IO。同步和非同步同步 IO 和非同步 IO 的區別就在於第二個步驟是否阻塞。如果實際的 IO 讀寫阻塞請求程序,那麼就是同步IO。因此常說的阻塞 IO、非阻塞 IO、IO 複用、訊號驅動 IO 都是同步 IO。如果不阻塞,而是作業系統幫你做完 IO 操作後再將結果返回給你(通知你),那麼就是非同步IO。IO 多路複用指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。目前支援 I/O 多路複用的常用系統呼叫有 select,pselect,poll,epoll 等,I/O 多路複用就是透過一種機制,一個程序可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但 select,pselect,poll,epoll本質上都是同步 I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而非同步 I/O 則無需自己負責進行讀寫,非同步 I/O 的實現會負責把資料從核心複製到使用者空間。

    5 種 IO 模型

    《UNIX 網路程式設計》對 IO 模型進行了總結,分別是:

    阻塞 IO、非阻塞 IO、IO 多路複用、訊號驅動的 IO、非同步 IO;前 4 種為同步 IO,只有非同步 IO 模型是非同步 IO。03回到問題目前很多高效能的基礎網路伺服器都是採用的 C 語言開發的,比如:Nginx、Redis、memcached 等,它們都是基於“事件驅動 + 事件回撥函式”的方式實現,也就是採用 epoll 等作為網路收發資料包的核心驅動。但不少人都認為“事件驅動 + 事件回撥函式”的程式設計方法是“反人類”的;因為大多數人都更習慣線性的處理一件事情:做完第一件事情再做第二件事情,並不習慣在 N 件事情之間頻繁的切換幹活。為了解決程式設計師在開發伺服器時需要自己的大腦不斷的“上下文切換”的問題,Go 語言引入了一種使用者態執行緒 goroutine 來取代編寫非同步的事件回撥函式,從而重新迴歸到多執行緒併發模型的線性、同步的程式設計方式上。在 Linux 上 Go 語言寫的網路伺服器也是採用的 epoll 作為最底層的資料收發驅動,Go 語言網路的底層實現中同樣存在“上下文切換”的工作,只是這個切換工作由 runtime 的排程器來做了,減少了程式設計師的負擔。

    所以,IO 等待是必然,只是誰等的問題。Go 語言在遇到 IO 需要等待時,runtime 會進行排程,語言層面處理這個問題。Go 擁有超高併發能力的關鍵就在於使用者態的 goroutine。

  • 中秋節和大豐收的關聯?
  • 有人說考辛斯復出的五個全明星的勇士隊比歷史上任何一局總冠軍球隊都要強,你怎麼看?