併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒

Coding挖掘機發表於2018-10-17

同步、非同步、阻塞、非阻塞,這四個概念很少人可以說清楚,不信的話,你可以先自己試著寫下來

同步和非同步

關注維度:訊息的通訊機制(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:

併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒

read(socket, buffer)
process(buffer)
複製程式碼

同步非阻塞IO:

併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒

while(read(socket,buffer) != SUCCESS);
process(buffer);
複製程式碼

IO多路複用:

併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒

select(socket);
while(1){
    sockets = select();
    for(socket in sockets){
        if(canRead(socket)){
            read(socket,buffer);
            process(buffer);
        }
    }
}
複製程式碼

雖然這種方式允許在單個執行緒中處理多個IO請求,但是每個IO請求的過程還是阻塞的,平均時間甚至比同步阻塞IO模型要更長

Reactor模式:

併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒
和多路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中,使用者執行緒收到通知時,資料已經被準備好,使用者執行緒可以直接使用(省略了讀取資料這一過程)

併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒
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.程式,程式,執行緒:

程式是我們寫的程式碼,需要對應到一個具體的程式來執行,程式間有獨立的記憶體地址,互不干擾。執行緒是程式的子任務,屬於同一程式的執行緒共享相同的記憶體。

程式讓程式之間的併發成為了可能,執行緒讓程式內的子任務併發成為了可能。

相關文章