【計算機內功心法】五:從小白到高手,你須要理解同步與異步

在這篇文章中咱們來討論一下到底什麼是同步,什麼是異步,以及在編程中這兩個概念到底意味着什麼,這些是進一步掌握高性能、高併發技術的基礎,所以很是關鍵。程序員

相信不少同窗遇到同步異步這兩個詞的時候大腦瞬間就像紅綠燈失靈的十字路口同樣陷入一片懵逼的狀態:數據庫

mengbi
mengbi

是的,這兩個看上去很像實際上也很像的詞彙給博主形成過很大的困擾,這兩個詞背後所表明的含義究竟是什麼呢?編程

咱們先從工做場景講起。網絡

苦逼程序員

假設如今老闆分配給了你一個很緊急而且很重要的任務,讓你下班前必須寫完(萬惡的資本主義)。爲了督促進度,老闆搬了個椅子坐在一邊盯着你寫代碼。併發

你內心確定已經罵上了「WTF,你有這麼閒嗎?盯着老子,你就不能去幹點其餘事情嗎?」app

老闆彷彿接收到了你的腦電波同樣:「我就在這等着,你寫完前我哪也不去,廁所也不去」異步

1600911423466
1600911423466

這個例子中老闆交給你任務後就一直等待什麼都不作直到你寫完,這個場景就是所謂的同步。函數

次日,老闆又交給了你一項任務。高併發

不過此次就沒那麼着急啦,此次老闆輕描淡寫「小夥子能夠啊,不錯不錯,你再努力幹一年,明年我就財務自由了,今天的這個任務不着急,你寫完告訴我一聲就行」。性能

此次老闆沒有盯着你寫代碼而是轉身刷視頻去了,你寫完後簡單的和老闆報告了一聲「我寫完了」。

1600911037338
1600911037338

這個例子老闆交代完任務就去忙其它事情,你完成任務後簡單的告訴老闆任務完成,這就是所謂的異步。

值得注意的是,在異步這種場景下重點是在你寫代碼的同時老闆在本身刷劇,這兩件事在同時進行所以這就是爲何通常來講異步比同步高效的本質所在,無論同步異步應用在什麼場景下。

所以,咱們能夠看到同步這個詞每每和任務的「依賴」、「關聯」、「等待」等關鍵詞相關,而異步每每和任務的「不依賴」,「無關聯」,「無需等待」,「同時發生」等關鍵詞相關。

By the way,若是遇到一個在身後盯着你寫代碼的老闆,三十六計走爲上策。

打電話與發郵件

做爲一名苦逼的程序員是不能只顧埋頭搬磚的,平時工做中的溝通免除不了,其中一種高效的溝通方式是吵架。。。啊不,是電話。

email
email

一般打電話時都是一我的在說另外一我的聽,一我的在說的時候另外一我的等待,等另外一我的說完後再接着說,所以在這個場景中你能夠看到,「依賴」、「關聯」、「等待」這些關鍵詞出現了,所以打電話這種溝通方式就是所謂的同步。

1600923556187
1600923556187

另外一種碼農經常使用的溝通方式是郵件。

郵件是另外一種必不可少溝通方式,由於沒有人傻等着你寫郵件什麼都不作,所以你能夠慢慢悠悠的寫,當你在寫郵件時收件人能夠去作一些像摸摸魚啊、上個廁所、和同時抱怨一下爲何十一假期不放兩週之類有意義的事情。

同時當你寫完郵件發出去後也不須要乾巴巴的等着對方什麼都不作,你也能夠作一些像摸魚之類這樣有意義的事情。

1600923768618
1600923768618

在這裏,你寫郵件別人摸魚,這兩件事又在同時進行,收件人和發件人都不須要相互等待,發件人寫完郵件的時候簡單的點個發送就能夠了,收件人收到後就能夠閱讀啦,收件人和發件人不須要相互依賴、不須要相互等待。

你看,在這個場景下「不依賴」,「無關聯」,「無需等待」這些關鍵詞就出現了,所以郵件這種溝通方式就是異步的。

同步調用

如今終於回到編程的主題啦。

既然如今咱們已經理解了同步與異步在各類場景下的意義(I hope so),那麼對於程序員來講該怎樣理解同步與異步呢?

咱們先說同步調用,這是程序員最熟悉的場景。

通常的函數調用都是同步的,就像這樣:

funcA() {
    // 等待函數funcB執行完成
    funcB();

    // 繼續接下來的流程
}

funcA調用funcB,那麼在funcB執行完前,funcA中的後續代碼都不會被執行,也就是說funcA必須等待funcB執行完成,就像這樣:

1600925448485
1600925448485

從上圖中咱們能夠看到,在funcB運行期間funcA什麼都作不了,這就是典型的同步。

注意,通常來講,像這種同步調用,funcA和funcB是運行在同一個線程中的,這是最爲常見的狀況。

但值得注意的是,即便運行在兩個不能線程中的函數也能夠進行同步調用,像咱們進行IO操做時實際上底層是經過系統調用(關於系統調用請參考《程序員應如何理解系統調用》)的方式向操做系統發出請求的,好比磁盤文件讀取:

read(file, buf);

這就是咱們在《讀取文件時,程序經歷了什麼》中描述的阻塞式I/O,在read函數返回前程序是沒法繼續向前推動的

read(file, buf);
// 程序暫停運行,
// 等待文件讀取完成後繼續運行

如圖所示:

1600925867319
1600925867319

只有當read函數返回後程序才能夠被繼續執行。

固然,這也是同步調用,可是和上面的同步調用不一樣的是,函數和被調函數運行在不一樣的線程中。

所以咱們能夠得出結論,同步調用和函數與被調函數是否運行在同一個線程是沒有關係的

在這裏咱們還要再次強調,同步方式下函數和被調函數沒法同時進行。

同步編程對程序員來講是最天然最容易理解的。

但容易理解的代價就是在一些場景下,注意,是在某些場景不是全部場景哦,同步並非高效的,由於任務沒有辦法同時進行。

接下來咱們看異步調用。

異步調用

有同步調用就有異步調用。

關於重要的異步調用,你能夠參考這裏

同步 vs 異步

咱們以常見的Web服務來舉例說明這一問題。

通常來講Web Server接收到用戶請求後會有一些典型的處理邏輯,最多見的就是數據庫查詢(固然,你也能夠把這裏的數據庫查詢換成其它I/O操做,好比磁盤讀取、網絡通訊等),在這裏咱們假定處理一次用戶請求須要通過步驟A、B、C而後讀取數據庫,數據庫讀取完成後須要通過步驟D、E、F,就像這樣:

# 處理一次用戶請求須要通過的步驟:

A;
B;
C;
數據庫讀取;
D;
E;
F

其中步驟A、B、C和D、E、F不須要任何I/O,也就是說這六個步驟不須要讀取文件、網絡通訊等,涉及到I/O操做的只有數據庫查詢這一步。

通常來講這樣的Web Server有兩個典型的線程:主線程和數據庫處理線程,注意,這討論的只是典型的場景,具體業務實際上可會有差異,但這並不影響咱們用兩個線程來講明問題。

首先咱們來看下最簡單的實現方式,也就是同步。

這種方式最爲天然也最爲容易理解:

// 主線程
main_thread() {
    A;
    B;
    C;
    發送數據庫查詢請求;
    D;
    E;
    F;
}

// 數據庫線程
DataBase_thread() {
    while(1) {
        數據庫讀取;
    }
}

這就是最爲典型的同步方法,主線程在發出數據庫查詢請求後就會被阻塞而暫停運行,直到數據庫查詢完畢後面的D、E、F才能夠繼續運行,就像這樣:

1600994106960
1600994106960

從圖中咱們能夠看到,主線程中會有「空隙」,這個空隙就是主線程的「休閒時光」,主線程在這段休閒時光中須要等待數據庫查詢完成才能繼續後續處理流程。

在這裏主線程就比如監工的老闆,數據庫線程就比如苦逼搬磚的程序員,在搬完磚前老闆什麼都不作只是牢牢的盯着你,等你搬完磚後纔去忙其它事情。

顯然,高效的程序員是不能容忍主線程偷懶的。

是時候祭出大殺器了,這是什麼大殺器呢,關於這個問題的答案你能夠參考這裏

總結

在這篇文章中咱們從各類場景分析了同步與異步這兩個概念,可是無論在什麼場景下,同步每每意味着雙方要相互等待、相互依賴,而異步意味着雙方相互獨立、各行其是。但願本篇能對你們理解這兩個重要的概念有所幫助。

相關文章
相關標籤/搜索