再談同步/異步與阻塞/非阻塞
幾年前寫過一篇描寫同步/異步以及阻塞/非阻塞的文章,今天回頭來看bug不少,于是需要重新整理一下原來的描述.
同步/異步
首先來解釋同步和異步的概念,這兩個概念與消息的通知機制有關.
舉個例子,比如我去銀行辦理業務,可以自己去排隊辦理,也可以叫人代辦,等他幫忙處理完了直接給我結果,對于要辦理這個銀行業務的人而言,自己去辦理是同步方式,而別人代辦完畢則是異步方式.區別在于,同步的方式下,操作者主動完成了這件事情.異步方式下,調用指令發出之后,操作馬上就返回了,操作者并不能馬上就知道結果了,而是等待所調用的異步過程(在這個例子中是幫忙代辦的人)處理完畢之后,通過通知手段(在代碼中通常是回調函數)來告訴操作者結果.
在上圖的異步IO模型中,應用程序調用完aio_read之后,不論是否有數據可讀,這個操作都會馬上返回,這個過程相當于這個例子中委托另一個人去幫忙代辦銀行業務的過程,當數據讀完拷貝到用戶內存之后,會發一個信號通知原進程告訴讀數據操作已經完成(而不僅僅是有數據可讀).
阻塞/非阻塞
接著解釋阻塞/非阻塞的概念,這兩個概念與程序處理事務時的狀態有關.
同樣是前面的例子,當真正執行辦理業務的人去辦理銀行業務時,前面可能已經有人排隊等候了.如果這個人一直從排隊到辦理完畢,中間都沒有做過其他的事情,那么這個過程就是阻塞的,這個人當前只在做這么一件事情.
在上圖中,應用程序發起recvfrom操作之后,要等待數據拷貝成功才能返回,這一整個過程中,不能做其它的操作,這個就是典型的阻塞IO.
反之,如果這個人發現前面排隊的人不少,于是選擇了出去逛逛,過了一會再回來看看有沒有輪到他的號被叫到,如果沒有又繼續出去逛過一陣再回來看看,如此以往,這個過程就是非阻塞的,因為處理這個事情的人,在這整個過程中,并沒有做到除了這件事之外不能再做別的事情,他的做法是反復的過來嘗試,如果沒有完成就下一次再次嘗試完成這件事情.
上圖與前面阻塞IO圖示的區別在于,當沒有數據可讀時,同樣的recvfrom操作返回了錯誤碼,表示當前沒有可讀數據,換言之,即使沒有數據也不會一直讓這個應用阻塞在這個調用上,這就是非阻塞IO.
到了這里,可以先簡單的小結一下這兩組概念了:
阻塞/非阻塞:區別在于完成一件事情時,是不是當事情還沒有完成時,處理這件事情的人除此之外不能再做別的事情.
同步/異步:是自己去做這件事情,還是等別人做好了來通知你做好了,然后自己去拿結果.注意,這里說的是拿結果.如果只是別人告訴你可以做某事,然后自己去操作,這種情況下也是同步的操作,在后面多路復用I/O中會進行闡述.
可見,兩組概念不是一個維度的概念.我們把需要辦理銀行業務的人稱為A,把代辦理的人稱為B,那么在A委托B辦理業務的情況下,假設A在交代B幫忙辦事之后,A就去做別的事情了,那么A并不存在針對辦理銀行業務這件事情而言是阻塞還是非阻塞,辦理事務時阻塞與否是針對真正需要辦理這件事情的人,也就是這個例子里的B.
與多路復用I/O的聯系
前幾年寫上一篇文章的時候,將多路復用I/O類的select/poll等和異步操作混為一談,在這里需要特別做一些補充說明.在以前的說明中,就這個例子而言,我列舉了一個情況,當去辦理業務的人,需要排隊時通常都會先去叫號拿到一個紙條上面寫了號碼,然后等待銀行叫號,在那個例子里面,我將銀行叫號比作select操作,把紙條比作向select注冊的回調函數,一旦可以進行操作的條件滿足,就會根據這個回調函數來通知辦理人,然后辦理人再去完成工作,因此select等多路復用操作是異步的行為.
上面那個例子,最大的錯誤在于,沒有意識到,同步與異步的區別在于是不是要求辦理者自己來完成,所有需要自己去完成操作的都是同步操作,不管是注冊了一個回調(這里的叫號小紙條)等待別人回調你,還是自己一直阻塞等待.在上面的例子中,雖然對需要辦理業務的人而言,通過叫號小紙條,他可以等待銀行的辦理通知,等待的同時可以去做點別的事情,比如看看手機什么的,但是只要可以辦理該業務的條件滿足,真正叫到了你的號可以辦理業務時,辦理者是需要自己去完成辦理的.
換言之,在完成一件事情時,這里需要區分處理兩種狀態:一是這個事情是不是可以做了(條件滿足的消息,如select告訴你某個文件描述符可讀),一是完成了這件事情(如調用read/write完成IO操作).多路復用IO做的,是它可以記錄下來有哪些人在等待消息的通知,在條件滿足時負責通知辦理者,而完成這件事情還是需要辦理者自己去完成.只要是自己去完成的操作,都是同步的操作.
UNP的6.2節中,最后對異步/同步做的總結是最準確的了:
POSIX defines these two terms as follows:
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.
Using these definitions, the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous I/O definition.