同步、非同步、阻塞、非阻塞,這四個概念很少人可以說清楚,不信的話,你可以先自己試著寫下來
同步和非同步
關注維度:訊息的通訊機制(synchronous communication/asynchronous communication)
判斷標準:呼叫者是否主動等待被呼叫者的返回結果
同步的理論說明:任務A的執行過程中呼叫了任務B。任務A對任務B發起呼叫後,主動等待呼叫結果。
同步的生活舉例:你去書店問老闆,是否有《作業系統》這本書,老闆說:稍等,我查一下。然後開始查啊查,等查好了,告訴你結果(主動等待被呼叫方返回結果)。
非同步的理論說明:任務A的執行過程中呼叫了任務B。任務A對任務B發起呼叫後,繼續執行後續工作。任務B完成後通過狀態、通知來通知呼叫者。
非同步的生活舉例:你去書店問老闆,是否有《作業系統》這本書,查好了打電話給你,然後直接掛電話了(此時被呼叫方不返回結果)。過了幾天,查好了,老闆主動打電話給你(被呼叫方回撥呼叫方,告知結果)。
複製程式碼
阻塞和非阻塞
關注維度:任務在等待呼叫結果時的狀態
判斷標準:呼叫方在等待被呼叫方的返回結果時,是否可以做其他事(是否被掛起)
阻塞的理論說明:任務A對任務B發起呼叫後,任務B需要執行一段時間才可返回結果,任務A選擇等待任務B的返回結果(暫時掛起)。
阻塞的生活舉例:你去書店問老闆,是否有《作業系統》這本書,你會一直把自己掛起,什麼是後不幹,一直在那等,直到得到返回結果。
非阻塞的理論說明:任務A對任務B發起呼叫後,與此同時,任務A在任務B執行的過程中去完成別的工作,等待任務B結果返回後再繼續(不掛起,而是繼續執行自己的任務)。
非阻塞的生活舉例:你去書店問老闆,是否有《作業系統》這本書,不管老闆有沒有告訴你,你自己都先去玩了(繼續執行自己的任務而不是乾等),但是也要偶爾也要check一下老闆是否有了結果。
複製程式碼
我們把使用者執行緒當做呼叫者,把核心執行緒當做被呼叫者,用幾張圖和簡單的示例程式碼描述一下當前流程的幾種I/O模型:
同步阻塞IO:
read(socket, buffer)
process(buffer)
複製程式碼
同步非阻塞IO:
while(read(socket,buffer) != SUCCESS);
process(buffer);
複製程式碼
IO多路複用:
select(socket);
while(1){
sockets = select();
for(socket in sockets){
if(canRead(socket)){
read(socket,buffer);
process(buffer);
}
}
}
複製程式碼
雖然這種方式允許在單個執行緒中處理多個IO請求,但是每個IO請求的過程還是阻塞的,平均時間甚至比同步阻塞IO模型要更長
Reactor模式:
和多路IO複用的差別在於,通過Reactor的方式,可以將使用者執行緒輪詢IO操作狀態的工作交給Reactor的事件迴圈進行處理,使用者執行緒註冊事件處理器之後可以繼續執行其他的工作,Reactor執行緒負責呼叫核心的select函式檢查socket狀態UserEventHandler handleEvent(){
if(canRead(socket)){
read(socket,buffer);
process(buffer);
}
}
Reactor.register(new UserEventHandler(socket));
Reactor.epollHandleEvents(){
while(1){
sockets = select();
for(socket in sockets){
getEventHandler(socket).handleEvent();
}
}
}
複製程式碼
IO多路複用還是使用了會阻塞執行緒的select系統呼叫,最多隻能算非同步阻塞IO,而非真正的非同步IO
非同步非阻塞IO:
在非同步阻塞IO中,使用者執行緒收到通知後自行讀取資料、處理資料。而在非同步非阻塞IO中,使用者執行緒收到通知時,資料已經被準備好,使用者執行緒可以直接使用(省略了讀取資料這一過程)
UserCompeletionHandler.handleEvent(buffer){ process(buffer); }aioRead(socket, new UserCompeletionHandler());
程式是什麼:
計算機最初發明的初衷是用於解決耗時耗力的複雜計算,是一個計算器
最原始的計算機執行程式的過程如下:等待使用者輸入指令->使用者輸入->計算機操作->等待使用者輸入指令->使用者輸入->計算機操作。在使用者思考或者輸入的過程中,計算機就空閒下來。
後來有了批處理系統,使用者可以把許多指令(如輸入1,輸入2)寫在磁碟中,計算機的執行過程變為:使用者輸入指令集合->取指令->執行->取指令->執行。
批處理系統大大提高了便捷性,但是還是存在一個問題,假設指令集合中有A,B兩個程式,當程式A進行I/O處理時,程式B只能等待程式A直到其執行完。也就是說,記憶體中只能有一個程式在執行。
複製程式碼
|程式One|程式Two| 計算機中有兩個程式
Time1: |記憶體| 記憶體中裝載程式One
Time2: |記憶體| 記憶體中裝載程式Two
複製程式碼
那麼,如何在記憶體中裝入多個程式呢?於是人們發明了程式,每個在執行的程式都看做一個程式,給每個程式分配合適大小的對應的記憶體地址空間,程式之間的空間互不干擾,並且儲存每個程式的執行狀態。通過程式之間的相互切換,使計算機看起來在一段時間內有幾個程式在同時執行。
複製程式碼
|程式One|程式Two| 計算機中有兩個程式
Time1: |部分記憶體|部分記憶體| 記憶體中裝載程式One,Two,分別在不同的部分。
複製程式碼
程式讓程式之間的併發成為了可能,從巨集觀上看,某個時間段內有多個程式在同時執行,但實際上在某一時刻只有一個程式(一部分的記憶體)會得到CPU,進行執行。
複製程式碼
執行緒是什麼:
程式讓程式之間的併發成為了可能,我們可以在電腦上同時聽歌,打字了(兩個不同的程式之間切換)。
複製程式碼
但是人們對程式實時性的要求越來越高。比如對QQ音樂來說,它不僅要處理使用者所傳送的互動請求,還要播放歌曲。假設某一時刻QQ音樂在播放歌曲,你點選了“暫停”按鈕,需要等待播放歌曲完畢之後才能處理“暫停”操作,這種程式肯定是不合格的。
於是人們把QQ音樂這個程式所對應的程式拆分成了多個執行緒,有播放歌曲的執行緒,處理互動請求的執行緒,每個執行緒負責一個獨立的子任務,這樣的話,我們點選了“暫停”按鈕,QQ音樂會釋放暫停播放歌曲的執行緒,讓互動請求的執行緒處理使用者的請求,響應完之後再切換回來,讓播放歌曲的執行緒得到CPU。具體過程如下:
播放執行緒---------------------掛起`````````````````播放執行緒----------
使用者點選暫停
互動執行緒--------處理完畢
複製程式碼
執行緒讓程式內的子任務併發成為了可能。
複製程式碼
3.程式,程式,執行緒:
程式是我們寫的程式碼,需要對應到一個具體的程式來執行,程式間有獨立的記憶體地址,互不干擾。執行緒是程式的子任務,屬於同一程式的執行緒共享相同的記憶體。
程式讓程式之間的併發成為了可能,執行緒讓程式內的子任務併發成為了可能。