Java IO 學習(一)同步/異步/阻塞/非阻塞


 關於IO,同步/異步/阻塞/非阻塞,這幾個關鍵詞是經常聽到的,譬如:

“Java oio是阻塞的,nio是非阻塞的”

“NodeJS的IO是異步的”

但是這些東西聽多了就容易迷糊,比方說同步是否就是阻塞,異步是否就是非阻塞呢?

 

先給出結論:

1. 異步/同步與阻塞/非阻塞之間沒有必然的聯系

2. 同步IO可以是阻塞,也可以是非阻塞的

3. 異步IO就是異步IO,它一定是非阻塞的,不存在異步阻塞IO這個說法

 

POSIX對同步/異步的定義如下,這兩句話非常關鍵

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;

 

再給出權威文檔:《UNIX網絡編程:卷一》的第六章

書中列出了如下五種IO模型:

  • 阻塞式I/O;

  • 非阻塞式I/O;

  • I/O復用(select,poll,epoll...);

  • 信號驅動式I/O(SIGIO);

  • 異步I/O(POSIX的aio_系列函數);

 

1. 阻塞式IO

我們手上有一個socket,現在希望能從這個socket里讀點數據出來,我們會對這個socket調用recvfrom方法

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
  struct sockaddr *src_addr, socklen_t *addrlen);

在默認情況下,recvfrom方法會被阻塞,直到從指定的socket上收到數據才會返回,返回時,buf中已經填充好了數據

阻塞的過程實際上可以分割成兩段:等待kernel准備好從網絡上接收到的數據報 + 等待收到的報文被從kernel復制到buf中

只有在這兩個過程全部完成后,recvfrom方法才會返回。

這就是阻塞式IO模型

 

 

2. 非阻塞式IO

還是上面的recvfrom方法,如果將其設置為非阻塞模式(flag與MSG_DONTWAIT異或),情況就會有所不同了:

在內核沒有准備好數據報時,調用recvfrom方法會立即返回異常碼(EWOULDBLOCK或者EAGAIN)(這一段是非阻塞的!)

如果內核已經准備好數據,調用recvfrom方法則會在數據報被從kernel拷貝到buf中后返回(這一段是同步的!)

也就是說,阻塞與非阻塞式IO的主要區別在於等待數據報准備好的第一階段,至於將數據從kernel拷貝到buf中的過程,兩者都是同步的。

 

但是個人覺得非阻塞式IO可能並不好用,因為在輪詢一個socket是否可讀的過程會直接占滿一個core

如果想要減少cpu資源占用的話,又會增加編程的復雜度。

 

3. I/O多路復用

IO多路復用有select/poll/epoll這樣的幾種方式

先介紹一下最有代表性的select方法

int select(int nfds, fd_set *restrict readfds,
  fd_set *restrict writefds, fd_set *restrict errorfds,
  struct timeval *restrict timeout);

select方法的返回值代表當前可以操作的fd數量,如果返回值大於0,說明已經有fd准備就緒,下一步我們就可以調用recvfrom方法從就緒的fd中讀取數據了(先只考慮可讀的情況)

select方法是否阻塞,與timeout參數有關

如果timeout被設置為0,那么select是非阻塞的,對select方法的調用會立即返回。

如果timeout被設置為非0,則select會阻塞,直到有fd可讀,或者timeout到期為止。

總的來說,I/O多路復用是同步阻塞的,但主要是阻塞在對select/poll/epoll方法的調用上,后續的recvfrom則是同步的。

多說一句,I/O多路復用,實際上跟第一條介紹的阻塞IO差不多
只是I/O多路復用可以同時監聽多個fd罷了

這樣就減少了為每個需要監聽的fd開啟一個線程的開銷。

 

4. 信號驅動式I/O

沒用過也沒見過,直接上截圖:

5. 異步I/O

同步IO中,在調用recvfrom方法時,即使kernel已經將數據准備好,recvfrom方法也不會立即返回

必須要在耗費一定的時間,將數據從kernel完全拷貝到用戶buf中后,recvfrom方法才會返回

也就是說,在recvfrom方法無異常返回的時候,數據已經在buf中准備好了

 

異步IO則有相當大的不同:

1. 用戶調用一次請求數據的方法,該方法會無阻塞的立即返回。

2. OS接到這個請求后,會將用戶所請求的數據從kernel拷貝到指定的位置。

3. 數據拷貝完成后,第一步中注冊的回調方法會被調用(或者觸發一個信號,總之就是要讓用戶感知到數據已經拷貝完成)

4. 用戶感知到這一事件,此時數據已經准備好,可以直接處理數據了

如下圖所示

但是目前Linux的aio還不成熟,而且epoll提供的IO多路復用模型在性能上已經夠用了,所以在此就不舉例了

ps. NodeJS在Linux上的異步實現是基於libeio,這是用阻塞IO和線程池模擬出來的異步IO

 

 

最后上一張圖作為總結

 

 最后再把文章開頭的兩句話再重復一遍,理解想必會更加深刻

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;

 

 

參考文獻

網絡編程釋疑之:同步,異步,阻塞,非阻塞


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM