欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中進(jìn)程、協(xié)程與線程的區(qū)別詳解

 更新時(shí)間:2023年08月04日 11:30:56   作者:小魏的博客  
這篇文章主要介紹了Java中進(jìn)程,線程,協(xié)程的概念、區(qū)別以及使用場(chǎng)景的選擇,早期的操作系統(tǒng)每個(gè)程序就是一個(gè)進(jìn)程,知道一個(gè)程序運(yùn)行完,才能進(jìn)行下一個(gè)進(jìn)程,就是"單進(jìn)程時(shí)代",一切的程序只能串行發(fā)生,需要的朋友可以參考下

進(jìn)程

我們知道,一切的軟件都是跑在操作系統(tǒng)上,真正用來干活 (計(jì)算) 的是 CPU。

早期的操作系統(tǒng)每個(gè)程序就是一個(gè)進(jìn)程,知道一個(gè)程序運(yùn)行完,才能進(jìn)行下一個(gè)進(jìn)程,就是 “單進(jìn)程時(shí)代”。

一切的程序只能串行發(fā)生。

早期的單進(jìn)程操作系統(tǒng),面臨 2 個(gè)問題:

  1. 單一的執(zhí)行流程,計(jì)算機(jī)只能一個(gè)任務(wù)一個(gè)任務(wù)處理。
  2. 進(jìn)程阻塞所帶來的 CPU 時(shí)間浪費(fèi)。

那么能不能有多個(gè)進(jìn)程來一起來執(zhí)行多個(gè)任務(wù)呢?

后來操作系統(tǒng)就具有了最早的并發(fā)能力:多進(jìn)程并發(fā),當(dāng)一個(gè)進(jìn)程阻塞的時(shí)候,切換到另外等待執(zhí)行的進(jìn)程,這樣就能盡量把 CPU 利用起來,CPU 就不浪費(fèi)了。

為了更合理的利用 CPU 資源,內(nèi)存劃分為多塊,不同進(jìn)程使用各自的內(nèi)存空間互不干擾,CPU 可以在多個(gè)進(jìn)程之間切換執(zhí)行,讓 CPU 的利用率變高。

為了實(shí)現(xiàn) CPU 在多個(gè)進(jìn)程之間切換,需要保存進(jìn)程的上下文(如程序計(jì)數(shù)器、棧等等),以便下次切換回來可以恢復(fù)執(zhí)行。

還需要一種調(diào)度算法,Linux 中采用了基于時(shí)間片和優(yōu)先級(jí)的完全公平調(diào)度算法。

 進(jìn)程的上下文切換涉及到從【用戶態(tài)】->【內(nèi)核態(tài)】->【用戶態(tài)】的過程,并且上下文中包含非常多的數(shù)據(jù),如下圖所示:

在多進(jìn)程 / 多線程的操作系統(tǒng)中,就解決了阻塞的問題,因?yàn)橐粋€(gè)進(jìn)程阻塞 cpu 可以立刻切換到其他進(jìn)程中去執(zhí)行,而且調(diào)度 cpu 的算法可以保證在運(yùn)行的進(jìn)程都可以被分配到 cpu 的運(yùn)行時(shí)間片。這樣從宏觀來看,似乎多個(gè)進(jìn)程是在同時(shí)被運(yùn)行。

但新的問題就又出現(xiàn)了,進(jìn)程擁有太多的資源,進(jìn)程的創(chuàng)建、切換、銷毀,都會(huì)占用很長(zhǎng)的時(shí)間,CPU 雖然利用起來了,但如果進(jìn)程過多,CPU 有很大的一部分都被用來進(jìn)行進(jìn)程調(diào)度了。

怎么才能提高 CPU 的利用率呢?

線程

1、介紹

多進(jìn)程的出現(xiàn)是為了解決 CPU 利用率的問題,那為什么還需要線程?答案是為了減少上下文切換時(shí)的開銷。

進(jìn)程在如下兩個(gè)時(shí)間點(diǎn)可能會(huì)讓出 CPU,進(jìn)行 CPU 切換:

  • 進(jìn)程阻塞,如網(wǎng)絡(luò)阻塞、代碼層面的阻塞(鎖、sleep等)、系統(tǒng)調(diào)用等
  • 進(jìn)程時(shí)間片用完,讓出 CPU

而進(jìn)程切換 CPU 時(shí)需要進(jìn)行這兩步:

  1. 切換頁目錄以使用新的地址空間
  2. 切換內(nèi)核棧和硬件上下文

進(jìn)程和線程在 Linux 中沒有本質(zhì)區(qū)別,他們最大的不同就是進(jìn)程有自己獨(dú)立的內(nèi)存空間,而線程(同進(jìn)程中)是共享內(nèi)存空間。

在進(jìn)程切換時(shí)需要轉(zhuǎn)換內(nèi)存地址空間,而線程切換沒有這個(gè)動(dòng)作,所以線程切換比進(jìn)程切換代價(jià)更小。

為什么內(nèi)存地址空間轉(zhuǎn)換這么慢?

Linux 實(shí)現(xiàn)中,每個(gè)進(jìn)程的地址空間都是虛擬的,虛擬地址空間轉(zhuǎn)換到物理地址空間需要查頁表,這個(gè)查詢是很慢的過程,因此會(huì)用一種叫做 TLB 的 cache 來加速,當(dāng)進(jìn)程切換后,TLB 也隨之失效了,所以會(huì)變慢。

綜上,線程是為了降低進(jìn)程切換過程中的開銷。

2、線程切換

從單線程應(yīng)用到多線程應(yīng)用帶來的不僅僅是好處。也會(huì)帶來開銷。

當(dāng)一個(gè)cpu從一個(gè)線程切換到另一個(gè)線程時(shí),cpu需要保存當(dāng)前線程的本地?cái)?shù)據(jù),程序當(dāng)前的指針等,然后加載下一個(gè)等待執(zhí)行的線程的本地?cái)?shù)據(jù),程序指針等。這種切換被稱之為上下文切換。cpu從執(zhí)行一個(gè)線程切換去執(zhí)行另一個(gè)線程。

 線程的上下文切換涉及到從【用戶態(tài)】->【內(nèi)核態(tài)】->【用戶態(tài)】的過程,上下文中包含的數(shù)據(jù)雖然不像進(jìn)程中的那么多,但整個(gè)過程也非常耗時(shí),具體包含的數(shù)據(jù)如下圖所示:

協(xié)程

1、介紹

很明顯,CPU 調(diào)度切換的是進(jìn)程和線程。盡管線程看起來很美好,但實(shí)際上多線程開發(fā)設(shè)計(jì)會(huì)變得更加復(fù)雜,要考慮很多同步競(jìng)爭(zhēng)等問題,如鎖、競(jìng)爭(zhēng)沖突等。

多進(jìn)程、多線程已經(jīng)提高了系統(tǒng)的并發(fā)能力,但是在當(dāng)今互聯(lián)網(wǎng)高并發(fā)場(chǎng)景下,為每個(gè)任務(wù)都創(chuàng)建一個(gè)線程是不現(xiàn)實(shí)的,因?yàn)闀?huì)消耗大量的內(nèi)存 (進(jìn)程虛擬內(nèi)存會(huì)占用 4GB [32 位操作系統(tǒng)],而線程也要大約 4MB)。

大量的進(jìn)程 / 線程出現(xiàn)了新的問題

  • 系統(tǒng)線程會(huì)占用非常多的內(nèi)存空間
  • 過多的線程切換會(huì)占用大量的系統(tǒng)時(shí)間。
  • 多線程開發(fā)涉及鎖、競(jìng)爭(zhēng)沖突等,開發(fā)復(fù)雜

當(dāng)我們的程序是 IO 密集型時(shí)(如 web 服務(wù)器、網(wǎng)關(guān)等),為了追求高吞吐,有兩種思路:

  1. 為每個(gè)請(qǐng)求開一個(gè)線程處理,為了降低線程的創(chuàng)建開銷,可以使用線程池技術(shù),理論上線程池越大,則吞吐越高,但線程池越大,CPU花在切換上的開銷也越大。
  2.  使用異步非阻塞的開發(fā)模型,用一個(gè)進(jìn)程或線程接收請(qǐng)求,然后通過 IO 多路復(fù)用讓進(jìn)程或線程不阻塞,省去上下文切換的開銷

這兩個(gè)方案,優(yōu)缺點(diǎn)都很明顯:方案1實(shí)現(xiàn)簡(jiǎn)單,但性能不高;方案2性能非常好,但實(shí)現(xiàn)起來復(fù)雜。

2、解決方案

有沒有介于這兩者之間的方案?既要簡(jiǎn)單,又要性能高,協(xié)程就解決了這個(gè)問題。

而協(xié)程剛好可以解決上述2個(gè)問題。

協(xié)程是用戶視角的一種抽象,操作系統(tǒng)并沒有協(xié)程的概念。

協(xié)程運(yùn)行在線程之上,協(xié)程的主要思想是在用戶態(tài)實(shí)現(xiàn)調(diào)度算法,用少量線程完成大量任務(wù)的調(diào)度。

協(xié)程需要解決線程遇到的幾個(gè)問題:

  • 內(nèi)存占用要小,且創(chuàng)建開銷要小
    • 用戶態(tài)的協(xié)程,可以設(shè)計(jì)的很小,可以達(dá)到 KB 級(jí)別。是線程的千分之一。
    • 線程??臻g通常是MB級(jí)別, 協(xié)程??臻g最小KB級(jí)別。
  • 減少上下文切換的開銷
  • 讓可執(zhí)行的線程盡量少,這樣切換次數(shù)必然會(huì)少
    • 讓線程盡可能的處于運(yùn)行狀態(tài),而不是阻塞讓出時(shí)間片
      • 多個(gè)協(xié)程多個(gè)協(xié)程綁定一個(gè)或者多個(gè)線程上
        • 當(dāng)一個(gè)協(xié)程執(zhí)行完成后,可以選擇主動(dòng)讓出,讓另一個(gè)協(xié)程運(yùn)行在當(dāng)前線程之上(分時(shí)復(fù)用)。
        • 即使有協(xié)程阻塞,該線程的其他協(xié)程也可以被 runtime 調(diào)度,轉(zhuǎn)移到其他可運(yùn)行的線程上。
  • 降低開發(fā)難度
    • goroutine是golang中對(duì)協(xié)程的實(shí)現(xiàn)
    • goroutine底層實(shí)現(xiàn)了少量線程干多事,減少切換時(shí)間等
    • 程序員可以輕松創(chuàng)建協(xié)程,無需去關(guān)注底層性能優(yōu)化的細(xì)節(jié)

 相較進(jìn)程和線程而言,協(xié)程的上下文切換則快了很多, 它只需在【用戶態(tài)】即可完成上下文的切換,并且需要切換的上下文信息也較少

進(jìn)程、線程、協(xié)程上下文切換開銷

為什么 【用戶態(tài)】->【內(nèi)核態(tài)】->【用戶態(tài)】這一過程比較耗時(shí),耗資源呢?

我們知道,操作系統(tǒng)保持跟蹤進(jìn)程運(yùn)行所需的所有狀態(tài)信息,這種狀態(tài),也就是上下文。

進(jìn)程的上下文包括許多信息,比如PC和寄存器文件的當(dāng)前值,以及主存的內(nèi)容。

在任何一個(gè)時(shí)刻,單處理器系統(tǒng)都只能執(zhí)行一個(gè)進(jìn)程的代碼。當(dāng)操作系統(tǒng)決定要把控制權(quán)從當(dāng)前進(jìn)程轉(zhuǎn)移到某個(gè)新進(jìn)程時(shí),就會(huì)進(jìn)行上下文切換,即保存當(dāng)前進(jìn)程的上下文、恢復(fù)新進(jìn)程的上下文,然后將控制權(quán)傳遞到新進(jìn)程。新進(jìn)程就會(huì)從它上次停止的地方開始。

假設(shè)現(xiàn)在有兩個(gè)并發(fā)的進(jìn)程:shel進(jìn)程和hello進(jìn)程。最開始,只有 shell進(jìn)程在運(yùn)行,即等待命令行上的輸人。當(dāng)我們讓它運(yùn)行hello程序時(shí), shell通過調(diào)用一個(gè)專門的函數(shù),即系統(tǒng)調(diào)用,來執(zhí)行我們的請(qǐng)求,系統(tǒng)調(diào)用會(huì)將控制權(quán)傳遞給操作系統(tǒng)。操作系統(tǒng)保存 shell進(jìn)程的上下文,創(chuàng)建一個(gè)新的hello進(jìn)程及其上下文,然后將控制權(quán)傳給新的hello進(jìn)程。hello進(jìn)程終止后,操作系統(tǒng)恢復(fù)shll進(jìn)程的上下文,并將控制權(quán)傳回給它, shell進(jìn)程會(huì)繼續(xù)等待下一個(gè)命令行輸入。

從上面這個(gè)實(shí)例我們可以得出結(jié)論:

(1)上一個(gè)進(jìn)程的上下文信息還在內(nèi)存和處理器當(dāng)中,我們要保存這些信息的話,就必須陷入到內(nèi)核態(tài)才可以。

(2)創(chuàng)建一個(gè)新的進(jìn)程,以及它的上下文信息,并且將控制權(quán)交給這個(gè)新進(jìn)程,這些都只有在內(nèi)核態(tài)才能實(shí)現(xiàn)。

綜上,我們可以得出結(jié)論,進(jìn)程和線程的上下文切換相較于協(xié)程比較“耗時(shí)耗力”。

那么協(xié)程的上下文切換相較線程有哪些提升?

協(xié)程上下文切換只涉及CPU上下文切換,而所謂的CPU上下文切換是指少量寄存器(PC / SP / DX)的值修改,協(xié)程切換非常簡(jiǎn)單,就是把當(dāng)前協(xié)程的 CPU 寄存器狀態(tài)保存起來,然后將需要切換進(jìn)來的協(xié)程的 CPU 寄存器狀態(tài)加載的 CPU 寄存器上就 ok 了。而對(duì)比線程的上下文切換則需要涉及模式切換(從用戶態(tài)切換到內(nèi)核態(tài))、以及 16 個(gè)寄存器、PC、SP…等寄存器的刷新;

線程棧空間通常是 2M, 協(xié)程??臻g最小 2K。

ChatGPT的回答

協(xié)程(Coroutine)和線程(Thread)都是常見的并發(fā)編程技術(shù)。線程是操作系統(tǒng)進(jìn)行調(diào)度的最小單位,而協(xié)程則是在應(yīng)用程序內(nèi)部進(jìn)行調(diào)度的最小單位。在多線程并發(fā)編程中,線程之間的切換涉及到內(nèi)核態(tài)和用戶態(tài)之間的切換,會(huì)帶來一定的開銷。而在協(xié)程并發(fā)編程中,由于很多協(xié)程都運(yùn)行在同一個(gè)線程內(nèi),因此協(xié)程之間的切換只涉及到用戶態(tài)之間的切換,避免了內(nèi)核態(tài)和用戶態(tài)之間的頻繁切換,從而提高了性能。

從底層角度來看,線程和協(xié)程之間的主要區(qū)別在于它們的調(diào)度方式和內(nèi)存模型。在線程模型中,每個(gè)線程都有自己的線程棧和寄存器狀態(tài),由操作系統(tǒng)進(jìn)行調(diào)度。線程之間的切換需要保存和恢復(fù)線程棧和寄存器狀態(tài),這會(huì)帶來一定的開銷。而在協(xié)程模型中,多個(gè)協(xié)程共享同一個(gè)線程棧和寄存器狀態(tài),由協(xié)程庫進(jìn)行調(diào)度。協(xié)程之間的切換只需要保存和恢復(fù)少量的狀態(tài)信息,避免了線程切換的大量開銷。

具體來說,在協(xié)程模型中,協(xié)程的狀態(tài)可以通過一個(gè)狀態(tài)機(jī)來表示。每個(gè)協(xié)程只有在遇到IO等需要等待的操作時(shí)才會(huì)主動(dòng)讓出CPU,轉(zhuǎn)而讓其他協(xié)程繼續(xù)執(zhí)行。當(dāng)IO操作完成后,該協(xié)程會(huì)被喚醒并從上次掛起的狀態(tài)恢復(fù)執(zhí)行。這種調(diào)度方式可以避免線程切換的開銷,并且允許多個(gè)協(xié)程在同一線程上進(jìn)行并發(fā)執(zhí)行,從而提高了性能。

總之,協(xié)程的快速切換和共享內(nèi)存模型使得它在一些場(chǎng)景下比線程更加高效,但是也需要注意到協(xié)程并發(fā)編程可能存在一些潛在的問題,例如上下文切換和調(diào)度算法等方面的性能問題和錯(cuò)誤處理等方面的編程難度問題。因此,在選擇編程模型時(shí)需要根據(jù)具體場(chǎng)景進(jìn)行權(quán)衡和選擇。

總結(jié)

從單進(jìn)程到多進(jìn)程提高了 CPU 利用率;從進(jìn)程到線程,降低了上下文切換的開銷;從線程到協(xié)程,進(jìn)一步降低了上下文切換的開銷,使得高并發(fā)的服務(wù)可以使用簡(jiǎn)單的代碼寫出來

協(xié)程(協(xié)同程序):同一時(shí)間只能執(zhí)行某個(gè)協(xié)程。開辟多個(gè)協(xié)程開銷不大。協(xié)程適合對(duì)某任務(wù)進(jìn)行分時(shí)處理。

線程:同一時(shí)間可以同時(shí)執(zhí)行多個(gè)線程。開辟多條線程開銷很大。線程適合多任務(wù)同時(shí)處理。

協(xié)程可以讓同一個(gè)線程執(zhí)行多個(gè)協(xié)程任務(wù)。降低了線程切換的開銷。

雖然協(xié)程也切換,但是由于多個(gè)線程共享線程棧和寄存器,因此協(xié)程切換就不涉及到線程棧和寄存器的開銷了,僅有協(xié)程本身一些狀態(tài)的變更。

到此這篇關(guān)于Java中進(jìn)程、協(xié)程與線程的區(qū)別詳解的文章就介紹到這了,更多相關(guān)Java進(jìn)程、協(xié)程與線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)之并查集

    詳解Java實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)之并查集

    并查集這種數(shù)據(jù)結(jié)構(gòu),可能出現(xiàn)的頻率不是那么高,但是還會(huì)經(jīng)常性的見到,其理解學(xué)習(xí)起來非常容易,通過本文,一定能夠輕輕松松搞定并查集
    2021-06-06
  • 老生常談Java網(wǎng)絡(luò)編程TCP通信(必看篇)

    老生常談Java網(wǎng)絡(luò)編程TCP通信(必看篇)

    下面小編就為大家?guī)硪黄仙U凧ava網(wǎng)絡(luò)編程TCP通信(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • Java8函數(shù)式接口java.util.function速查大全

    Java8函數(shù)式接口java.util.function速查大全

    因?yàn)镴ava8引入了函數(shù)式接口,在java.util.function包含了幾大類函數(shù)式接口聲明,這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式接口java.util.function速查的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • SpringBoot集成Kafka的實(shí)現(xiàn)示例

    SpringBoot集成Kafka的實(shí)現(xiàn)示例

    本文主要介紹了SpringBoot集成Kafka的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-01-01
  • js中去除字符串中所有的html標(biāo)簽代碼實(shí)例

    js中去除字符串中所有的html標(biāo)簽代碼實(shí)例

    這篇文章主要介紹了js中去除字符串中所有的html標(biāo)簽代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 獲取JsonObject某一未知key的值操作

    獲取JsonObject某一未知key的值操作

    這篇文章主要介紹了獲取JsonObject某一未知key的值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • java實(shí)現(xiàn)Excel的導(dǎo)入、導(dǎo)出

    java實(shí)現(xiàn)Excel的導(dǎo)入、導(dǎo)出

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)Excel的導(dǎo)入、導(dǎo)出的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • SpringBoot @SpringBootTest加速單元測(cè)試的小訣竅

    SpringBoot @SpringBootTest加速單元測(cè)試的小訣竅

    這篇文章主要介紹了SpringBoot @SpringBootTest加速單元測(cè)試的小訣竅,具有很好的參考價(jià)值,對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Jenkins一鍵打包部署SpringBoot應(yīng)用

    Jenkins一鍵打包部署SpringBoot應(yīng)用

    本文主要介紹了Jenkins一鍵打包部署SpringBoot應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Android Studio中ButterKnife插件的安裝與使用詳解

    Android Studio中ButterKnife插件的安裝與使用詳解

    本篇文章主要介紹了Android Studio中ButterKnife插件的安裝與使用詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01

最新評(píng)論