免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

帶你通俗易懂了解進(jìn)程、線程和協(xié)程

 塞北de雪 2024-10-21

作者:肖瑋

本故事采用簡(jiǎn)潔明了的對(duì)話方式,盡洪荒之力讓你在輕松無負(fù)擔(dān)的氛圍中,稍微深入地理解進(jìn)程、線程和協(xié)程的相關(guān)原理知識(shí)

寫在最前

本故事采用簡(jiǎn)潔明了的對(duì)話方式,盡洪荒之力讓你在輕松無負(fù)擔(dān)的氛圍中,稍微深入地理解進(jìn)程、線程和協(xié)程的相關(guān)原理知識(shí)

如果你覺得自己本來就已經(jīng)理解得很透徹了,那也不妨瞧一瞧,指不定有意外的收獲呢

在這個(gè) AI 內(nèi)容生成泛濫的時(shí)代,依然有一批人"傻傻"堅(jiān)持原創(chuàng),如果您能讀到最后,還請(qǐng)點(diǎn)贊或收藏或關(guān)注支持下我唄,感謝 ( ̄︶ ̄)↗

進(jìn)程

丹尼爾:蛋兄,我對(duì)進(jìn)程、線程、協(xié)程這些概念似懂非懂的,要不咱們今天就好好聊聊這些?

蛋先生:當(dāng)然可以

丹尼爾:先說說進(jìn)程吧,從字面意思上看,是不是可以理解為正在運(yùn)(進(jìn))行的程序?

蛋先生:正是如此,程序是靜態(tài)的,而進(jìn)程則是動(dòng)態(tài)的

丹尼爾:說得我更糊涂了

蛋先生:好吧,以你電腦上的視頻播放器(就是一個(gè)程序)為例。當(dāng)你不雙擊它時(shí),它就是一個(gè)安靜的美男子——哦不,就是一份靜靜躺在硬盤上的代碼

丹尼爾:別逗我了,蛋兄

蛋先生:( ╯▽╰) 但當(dāng)你雙擊它時(shí),它就通過進(jìn)程“動(dòng)”起來了

丹尼爾:進(jìn)程做了什么讓它“動(dòng)”起來了?

蛋先生:程序是代碼,比如播放邏輯的代碼。要讓視頻播放,這些代碼必須執(zhí)行起來對(duì)吧

丹尼爾:確實(shí)。那進(jìn)程是怎么執(zhí)行這些代碼的?

蛋先生:進(jìn)程會(huì)利用操作系統(tǒng)的調(diào)度器分配給它的 CPU 時(shí)間片,通過 CPU 來執(zhí)行代碼(注意:現(xiàn)代操作系統(tǒng)都是直接調(diào)度線程,不會(huì)調(diào)度進(jìn)程哦)

丹尼爾:原來如此,操作系統(tǒng)給進(jìn)程分配了 CPU 時(shí)間片資源。那還有其他的資源嗎?

蛋先生:代碼執(zhí)行過程,需要存儲(chǔ)一些數(shù)據(jù),所以進(jìn)程還分配有內(nèi)存空間資源

丹尼爾:都存些什么數(shù)據(jù)呢?

蛋先生:程序代碼本身就需要先存儲(chǔ)起來。然后代碼執(zhí)行過程中的變量,參數(shù)什么的,也是需要存儲(chǔ)的。給個(gè)圖你了解一下吧

圖片

丹尼爾:哦,還有其它資源嗎?

蛋先生:程序可能會(huì)執(zhí)行一些 I/O 任務(wù),比如視頻播放器需要加載視頻,這些視頻數(shù)據(jù)可能從本地文件加載,也可能從網(wǎng)絡(luò)上加載,這就需要文件描述符資源。計(jì)算,存儲(chǔ),I/O 涉及的三大資源,就是分配給進(jìn)程最主要的資源了。而進(jìn)程就是分配資源的基本單位了

丹尼爾:原來如此,代碼執(zhí)行,數(shù)據(jù)存儲(chǔ),I/O 操作,程序就能運(yùn)行起來了

蛋先生:正是這樣。有了進(jìn)程,我們可以同時(shí)運(yùn)行多個(gè)程序。比如,你可以一邊播放視頻,一邊編輯文檔,每個(gè)程序都有自己的進(jìn)程,互不干擾。即使它們都是同一份代碼,但各自播放的內(nèi)容和進(jìn)度都可以不同

丹尼爾:明白了

蛋先生:既然你有編程基礎(chǔ),我就簡(jiǎn)單總結(jié)一下吧。

什么是進(jìn)程?進(jìn)程就是程序的實(shí)例(就像面向?qū)ο缶幊讨械念?,類是靜態(tài)的,只有實(shí)例化后才運(yùn)行,且同一個(gè)類可以有多個(gè)實(shí)例)

為什么需要進(jìn)程?為了讓程序運(yùn)行起來(如果程序不運(yùn)行,用戶昨看視頻捏)

線程

丹尼爾:這個(gè)總結(jié)我喜歡,接下來該聊聊線程了

蛋先生:進(jìn)程(可以看成只有一個(gè)線程的進(jìn)程)同時(shí)只能做一件事,所以你的視頻播放器的工作方式就像以下

圖片

丹尼爾:那樣的體驗(yàn)肯定糟糕透了,視頻完全加載并解碼完之前,啥都看不了

蛋先生:沒錯(cuò),所以我們期望能夠一邊加載和解碼,一邊播放,這樣就不會(huì)浪費(fèi)時(shí)間空等了。為了實(shí)現(xiàn)這個(gè)目的,一個(gè)進(jìn)程就需要進(jìn)化成多個(gè)線程來同時(shí)執(zhí)行多個(gè)任務(wù)

圖片

丹尼爾:那如果一個(gè)進(jìn)程只能做一件事,我用兩個(gè)進(jìn)程不也可以同時(shí)做兩件事嗎?

圖片

蛋先生:你說得對(duì),但進(jìn)程間是完全獨(dú)立的,互不干擾。而線程則共享同一個(gè)進(jìn)程的資源,所以線程間交換數(shù)據(jù)更方便,幾乎沒有通訊損耗。但進(jìn)程間交換數(shù)據(jù)就麻煩多了,得通過一些通訊機(jī)制,比如管道、消息隊(duì)列之類的

想象一下,我和你住在不同的房子,你要寄給我一箱牛奶,就得通過快遞等方式寄給我。但如果我和你住在同一個(gè)房子,你買了牛奶只要往冰箱一放,我只要去冰箱一拿,多方便啊

圖片

丹尼爾:那線程都共享進(jìn)程的什么資源呢?

蛋先生:分配給進(jìn)程的資源,絕大部分都是線程間共享的。比如內(nèi)存空間的代碼段,數(shù)據(jù)段,堆,比如文件描述符等。而棧則是每個(gè)線程特有的,因?yàn)榫€程是程序執(zhí)行的最小單位,它需要記錄自己的局部變量等

共享資源覆蓋

丹尼爾:線程之間共享資源,總感覺會(huì)有什么問題

蛋先生:大部分情況下線程之間還是可以和平共處的,但有一種情況,就是大家都想對(duì)同個(gè)資源進(jìn)行寫操作時(shí),就會(huì)發(fā)生覆蓋,導(dǎo)致數(shù)據(jù)不一致等問題

丹尼爾:能具體說一說嗎?

蛋先生:為了更容易理解,我們借助以下代碼來說明。如果兩個(gè)線程來運(yùn)行 main 方法,會(huì)有概率出現(xiàn)一些讓你費(fèi)解的結(jié)果

public class Main {
    // 定義一個(gè)靜態(tài)成員變量 a
    private static int a = 1;

    // 定義一個(gè)方法 add 來增加 a 的值
    public static void add() {
        a += 1;
    }

    public static void main(String[] args) {
        add();
        System.out.println("a 的值是: " + a); // 輸出 a 的值
    }
}

丹尼爾:怎么說?

蛋先生:a 是個(gè)靜態(tài)成員變量,它存儲(chǔ)在進(jìn)程內(nèi)存空間的數(shù)據(jù)段,共享于多個(gè)線程,所以它屬于線程間共享的資源對(duì)吧

丹尼爾:沒錯(cuò)

蛋先生:我們?cè)倏聪?add 方法的邏輯 a += 1, 這么簡(jiǎn)單的代碼,在底層并非原子操作,而是分為三個(gè)步驟

步驟一:獲取 a 變量的值

步驟二:執(zhí)行 +1 運(yùn)算

步驟三:將運(yùn)行結(jié)果賦值給 a

丹尼爾:那會(huì)有什么問題呢?

蛋先生:如果線程 1 在執(zhí)行完步驟一和步驟二,還沒執(zhí)行步驟三時(shí),操作系統(tǒng)進(jìn)行了 CPU 調(diào)度,發(fā)生了線程切換,使得線程 2 也開始執(zhí)行步驟一和步驟二。接下來線程 1 和線程 2 都會(huì)各自執(zhí)行步驟三。因?yàn)?add 方法執(zhí)行了兩次,正確的結(jié)果 a 的值應(yīng)該是 +2。但很遺憾,結(jié)果是 +1。這樣的結(jié)果有時(shí)候會(huì)讓你摸不著頭腦,而不穩(wěn)定的結(jié)果也將會(huì)導(dǎo)致應(yīng)用的不穩(wěn)定

丹尼爾:啊,是這樣啊。那該怎么辦?

蛋先生:解決方法有很多種,比如加鎖方案,比如無鎖方案等,需要根據(jù)實(shí)際情況選擇。這個(gè)話題比較復(fù)雜,我們后面再找時(shí)間詳細(xì)探討吧?,F(xiàn)在只要知道多線程會(huì)有資源覆蓋的問題就行了

上下文切換

丹尼爾:好的,明白了。剛才提到線程切換,線程切換到底發(fā)生了什么呢?

蛋先生:線程切換會(huì)進(jìn)行線程上下文切換。線程在運(yùn)行時(shí),實(shí)際上是在執(zhí)行代碼,而執(zhí)行代碼過程中需要存儲(chǔ)一些中間數(shù)據(jù),也可能會(huì)執(zhí)行一些 I/O 操作。如果過程中被中斷,是不是得保留現(xiàn)場(chǎng),以便下次恢復(fù)繼續(xù)運(yùn)行?

丹尼爾:嗯,確實(shí)需要,但具體都存儲(chǔ)些什么呢?

蛋先生:首先是下一個(gè)要執(zhí)行的代碼,這個(gè)存儲(chǔ)在程序計(jì)數(shù)器中。然后是一些中間數(shù)據(jù)如局部變量等,會(huì)存儲(chǔ)在線程棧中。為了加速計(jì)算,中間數(shù)據(jù)中對(duì)當(dāng)前指令執(zhí)行至關(guān)重要的部分會(huì)存儲(chǔ)在寄存器中。所以,程序計(jì)數(shù)器需要保存,寄存器需要保存,線程棧指針也需要保存

丹尼爾:“中間數(shù)據(jù)中對(duì)當(dāng)前指令執(zhí)行至關(guān)重要的部分會(huì)存儲(chǔ)在寄存器”,能舉個(gè)例子嗎?

蛋先生:假設(shè)以下代碼,當(dāng)在執(zhí)行 add 方法時(shí),x, y, a, b 會(huì)壓進(jìn)線程棧中。而其中 a, b 是和當(dāng)前運(yùn)算最相關(guān)的,則會(huì)存儲(chǔ)在寄存器中,以加速 CPU 的運(yùn)算

int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 10;
    int y = 20;
    int result = add(x, y);
    return 0
}

協(xié)程

丹尼爾:哦,原來如此。線程已經(jīng)相當(dāng)不錯(cuò)了,那協(xié)程又是怎么回事呢?

蛋先生:回想一下,我們之前一個(gè)線程負(fù)責(zé)運(yùn)行加載和解碼邏輯,另一個(gè)線程負(fù)責(zé)播放邏輯,對(duì)吧?

丹尼爾:沒錯(cuò),有什么問題嗎?

蛋先生:其實(shí)還有優(yōu)化的空間。線程在執(zhí)行加載視頻片段時(shí),必須等待結(jié)果返回才能執(zhí)行解碼操作

圖片

丹尼爾:確實(shí),加載片段的等待時(shí)間似乎又被浪費(fèi)了

蛋先生:沒錯(cuò),我們可以充分利用這段時(shí)間。只需讓線程在加載的同時(shí)進(jìn)行解碼,就能大幅減少加載等待的時(shí)間。而這正是協(xié)程所能發(fā)揮的作用

圖片

丹尼爾:哇,蛋兄,你可真是個(gè)會(huì)過日子的人,這么精打細(xì)算。但我只要用不同的線程分別處理加載和解碼,不也能達(dá)到同樣的效果嗎?

蛋先生:可以是可以,但多線程會(huì)帶來一些問題

丹尼爾:啥問題呢?

蛋先生:首先,一個(gè)線程用于執(zhí)行加載操作,這主要是 I/O 操作,幾乎不消耗 CPU 資源,導(dǎo)致該線程長(zhǎng)時(shí)間處于阻塞狀態(tài),這是很浪費(fèi)的。當(dāng)然,你可以讓它休眠以釋放 CPU 時(shí)間,但創(chuàng)建線程本身就有開銷,線程切換同樣有開銷。相比之下,協(xié)程非常輕量,創(chuàng)建和切換的開銷極小

丹尼爾:為什么協(xié)程的創(chuàng)建和切換的開銷極小呢?

蛋先生:主要是因?yàn)樗⒎遣僮飨到y(tǒng)層面的東西,就不涉及內(nèi)核調(diào)度。一般是由編程語(yǔ)言來實(shí)現(xiàn)(比如 Python 的 asyncio 標(biāo)準(zhǔn)庫(kù)),它屬于用戶態(tài)的東西

丹尼爾:那協(xié)程不會(huì)有像多線程那樣的資源覆蓋問題嗎?

蛋先生:線程的執(zhí)行時(shí)機(jī)由操作系統(tǒng)調(diào)度,程序員無法控制,這正是多線程容易出現(xiàn)資源覆蓋的主要原因。而協(xié)程的執(zhí)行時(shí)機(jī)由程序自身控制,不受操作系統(tǒng)調(diào)度影響,因此可以完全避免這類問題

此外,同一個(gè)線程內(nèi)的多個(gè)協(xié)程共享同一個(gè)線程的 CPU 時(shí)間片資源,它們?cè)?CPU 上的執(zhí)行是有先后順序的,不能并行執(zhí)行。而線程是可以并行執(zhí)行的

丹尼爾:那協(xié)程是如何實(shí)現(xiàn)這一點(diǎn)的呢?

蛋先生:協(xié)程(coroutine),其實(shí)是一種特殊的子程序(subroutine,比如普通函數(shù))。普通函數(shù)一旦執(zhí)行就會(huì)從頭到尾運(yùn)行,然后返回結(jié)果,中間不會(huì)暫停。而協(xié)程則可以在執(zhí)行到一半時(shí)暫停。利用這一特性,我們可以在遇到 I/O 這類不消耗 CPU 資源的操作時(shí),將其掛起,繼續(xù)執(zhí)行其他計(jì)算任務(wù),充分利用 CPU 資源。等 I/O 操作結(jié)果返回時(shí),再恢復(fù)執(zhí)行

丹尼爾:感覺很像 NodeJS 的異步 I/O 啊

蛋先生:沒錯(cuò),它們的目的都是在一個(gè)線程內(nèi)并發(fā)執(zhí)行多個(gè)任務(wù)。不過在叫法和實(shí)現(xiàn)上會(huì)有一些差異

丹尼爾:感覺今天了解得夠多了,謝謝蛋兄

蛋先生:后會(huì)有期!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多