成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

帶你通俗易懂了解進程、線程和協程

系統
本故事采用簡潔明了的對話方式,盡洪荒之力讓你在輕松無負擔的氛圍中,稍微深入地理解進程、線程和協程的相關原理知識。

作者 | 肖瑋

寫在最前

本故事采用簡潔明了的對話方式,盡洪荒之力讓你在輕松無負擔的氛圍中,稍微深入地理解進程、線程和協程的相關原理知識

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

在這個 AI 內容生成泛濫的時代,依然有一批人"傻傻"堅持原創,如果您能讀到最后,還請點贊或收藏或關注支持下我唄,感謝 ( ̄︶ ̄)↗

進程

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

蛋先生:當然可以

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

蛋先生:正是如此,程序是靜態的,而進程則是動態的

丹尼爾:說得我更糊涂了

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

丹尼爾:別逗我了,蛋兄

蛋先生:( ╯▽╰) 但當你雙擊它時,它就通過進程“動”起來了

丹尼爾:進程做了什么讓它“動”起來了?

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

丹尼爾:確實。那進程是怎么執行這些代碼的?

蛋先生:進程會利用操作系統的調度器分配給它的 CPU 時間片,通過 CPU 來執行代碼(注意:現代操作系統都是直接調度線程,不會調度進程哦)

丹尼爾:原來如此,操作系統給進程分配了 CPU 時間片資源。那還有其他的資源嗎?

蛋先生:代碼執行過程,需要存儲一些數據,所以進程還分配有內存空間資源

丹尼爾:都存些什么數據呢?

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

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

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

丹尼爾:原來如此,代碼執行,數據存儲,I/O 操作,程序就能運行起來了

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

丹尼爾:明白了

蛋先生:既然你有編程基礎,我就簡單總結一下吧。

什么是進程?進程就是程序的實例(就像面向對象編程中的類,類是靜態的,只有實例化后才運行,且同一個類可以有多個實例)

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

線程

丹尼爾:這個總結我喜歡,接下來該聊聊線程了

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

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

蛋先生:沒錯,所以我們期望能夠一邊加載和解碼,一邊播放,這樣就不會浪費時間空等了。為了實現這個目的,一個進程就需要進化成多個線程來同時執行多個任務

丹尼爾:那如果一個進程只能做一件事,我用兩個進程不也可以同時做兩件事嗎?

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

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

丹尼爾:那線程都共享進程的什么資源呢?

蛋先生:分配給進程的資源,絕大部分都是線程間共享的。比如內存空間的代碼段,數據段,堆,比如文件描述符等。而棧則是每個線程特有的,因為線程是程序執行的最小單位,它需要記錄自己的局部變量等

共享資源覆蓋

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

蛋先生:大部分情況下線程之間還是可以和平共處的,但有一種情況,就是大家都想對同個資源進行寫操作時,就會發生覆蓋,導致數據不一致等問題

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

蛋先生:為了更容易理解,我們借助以下代碼來說明。如果兩個線程來運行 main 方法,會有概率出現一些讓你費解的結果

public class Main {
    // 定義一個靜態成員變量 a
    private static int a = 1;

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

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

丹尼爾:怎么說?

蛋先生:a 是個靜態成員變量,它存儲在進程內存空間的數據段,共享于多個線程,所以它屬于線程間共享的資源對吧

丹尼爾:沒錯

蛋先生:我們再看下 add 方法的邏輯 a += 1, 這么簡單的代碼,在底層并非原子操作,而是分為三個步驟

  • 步驟一:獲取 a 變量的值
  • 步驟二:執行 +1 運算
  • 步驟三:將運行結果賦值給 a

丹尼爾:那會有什么問題呢?

蛋先生:如果線程 1 在執行完步驟一和步驟二,還沒執行步驟三時,操作系統進行了 CPU 調度,發生了線程切換,使得線程 2 也開始執行步驟一和步驟二。接下來線程 1 和線程 2 都會各自執行步驟三。因為 add 方法執行了兩次,正確的結果 a 的值應該是 +2。但很遺憾,結果是 +1。這樣的結果有時候會讓你摸不著頭腦,而不穩定的結果也將會導致應用的不穩定

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

蛋先生:解決方法有很多種,比如加鎖方案,比如無鎖方案等,需要根據實際情況選擇。這個話題比較復雜,我們后面再找時間詳細探討吧。現在只要知道多線程會有資源覆蓋的問題就行了

上下文切換

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

蛋先生:線程切換會進行線程上下文切換。線程在運行時,實際上是在執行代碼,而執行代碼過程中需要存儲一些中間數據,也可能會執行一些 I/O 操作。如果過程中被中斷,是不是得保留現場,以便下次恢復繼續運行?

丹尼爾:嗯,確實需要,但具體都存儲些什么呢?

蛋先生:首先是下一個要執行的代碼,這個存儲在程序計數器中。然后是一些中間數據如局部變量等,會存儲在線程棧中。為了加速計算,中間數據中對當前指令執行至關重要的部分會存儲在寄存器中。所以,程序計數器需要保存,寄存器需要保存,線程棧指針也需要保存

丹尼爾:“中間數據中對當前指令執行至關重要的部分會存儲在寄存器”,能舉個例子嗎?

蛋先生:假設以下代碼,當在執行 add 方法時,x, y, a, b 會壓進線程棧中。而其中 a, b 是和當前運算最相關的,則會存儲在寄存器中,以加速 CPU 的運算

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

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

協程

丹尼爾:哦,原來如此。線程已經相當不錯了,那協程又是怎么回事呢?

蛋先生:回想一下,我們之前一個線程負責運行加載和解碼邏輯,另一個線程負責播放邏輯,對吧?

丹尼爾:沒錯,有什么問題嗎?

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

丹尼爾:確實,加載片段的等待時間似乎又被浪費了

蛋先生:沒錯,我們可以充分利用這段時間。只需讓線程在加載的同時進行解碼,就能大幅減少加載等待的時間。而這正是協程所能發揮的作用

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

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

丹尼爾:啥問題呢?

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

丹尼爾:為什么協程的創建和切換的開銷極小呢?

蛋先生:主要是因為它并非操作系統層面的東西,就不涉及內核調度。一般是由編程語言來實現(比如 Python 的 asyncio 標準庫),它屬于用戶態的東西

丹尼爾:那協程不會有像多線程那樣的資源覆蓋問題嗎?

蛋先生:線程的執行時機由操作系統調度,程序員無法控制,這正是多線程容易出現資源覆蓋的主要原因。而協程的執行時機由程序自身控制,不受操作系統調度影響,因此可以完全避免這類問題

此外,同一個線程內的多個協程共享同一個線程的 CPU 時間片資源,它們在 CPU 上的執行是有先后順序的,不能并行執行。而線程是可以并行執行的

丹尼爾:那協程是如何實現這一點的呢?

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

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

蛋先生:沒錯,它們的目的都是在一個線程內并發執行多個任務。不過在叫法和實現上會有一些差異

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

蛋先生:后會有期!

責任編輯:趙寧寧 來源: 騰訊技術工程
相關推薦

2011-10-26 19:57:33

2019-06-19 08:30:47

網絡協議IPTCP

2009-12-31 10:59:22

ADSL技術原理

2022-10-11 11:35:29

自動駕駛

2022-09-23 08:32:53

微服務架構服務

2023-12-13 09:56:13

?多進程多線程協程

2023-08-03 16:02:24

Objectwaitnotify

2023-01-06 09:40:20

項目性能

2020-06-08 10:50:58

前端TypeScript代碼

2019-05-20 07:37:00

TCPIP網絡協議

2021-05-26 16:12:20

區塊鏈加密貨幣比特幣

2018-01-17 22:36:46

區塊鏈數字貨幣比特幣

2022-06-28 07:31:11

哨兵模式redis

2018-06-05 15:41:22

進程線程協程

2023-12-17 14:24:46

計算機進程線程

2021-11-04 08:16:50

MySQL SQL 語句數據庫

2022-07-06 08:17:50

C 語言函數選型

2018-03-29 06:40:26

物聯網

2018-03-11 14:57:07

物聯網組網無線通信

2018-03-05 08:23:40

物聯網互聯網網絡技術
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久国产精品亚洲 | 国产91视频一区二区 | 国产精品自产拍 | 日韩国产精品一区二区三区 | 国产在线看片 | 欧美视频二区 | 少妇诱惑av | 亚洲成人精品 | 狠狠干美女 | 婷婷狠狠 | 国产成人综合一区二区三区 | 久久久精品久久 | 亚洲视频一区二区三区 | 日日夜夜av | 日本免费一区二区三区 | 日韩国产欧美一区 | 国产精品久久久久久久久久久久久 | 秋霞在线一区二区 | 91精品久久 | 影音先锋中文字幕在线观看 | 免费看淫片 | 在线播放国产一区二区三区 | 久久精品一区 | 另类视频在线 | 成人黄色电影在线观看 | 欧美午夜视频 | 久久久激情视频 | 午夜免费观看 | 午夜一区二区三区在线观看 | 午夜丰满寂寞少妇精品 | 日韩成人影院 | 久久久免费精品 | av国产精品 | 久久久999国产精品 中文字幕在线精品 | 中文字幕日韩欧美 | 精品国产一区二区国模嫣然 | 91精品国产91久久久久游泳池 | 日本一区二区三区在线观看 | 中文字幕免费在线 | 久久y| 欧美日韩在线观看视频网站 |