進(jìn)程,線程
對(duì)于操作系統(tǒng)來(lái)說(shuō),一個(gè)任務(wù)就是一個(gè)進(jìn)程(Process).
有些進(jìn)程還不止同時(shí)干一件事,比如Word,它可以同時(shí)進(jìn)行打字、拼寫檢查、打印等事情。在一個(gè)進(jìn)程內(nèi)部,要同時(shí)干多件事,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
由于每個(gè)進(jìn)程至少要干一件事,所以,一個(gè)進(jìn)程至少有一個(gè)線程。
線程是最小的執(zhí)行單元,而進(jìn)程由至少一個(gè)線程組成,也是系統(tǒng)分配資源的最小單元,資,源分配給進(jìn)程,同一個(gè)進(jìn)程的所有線程共享該進(jìn)程所有資源。如何調(diào)度進(jìn)程和線程,完全由操作系統(tǒng)決定,程序自己不能決定什么時(shí)候執(zhí)行,執(zhí)行多長(zhǎng)時(shí)間。
線程和進(jìn)程的關(guān)系
1、一個(gè)進(jìn)程可以有多個(gè)線程,但至少有一個(gè)線程;而一個(gè)線程只能在一個(gè)進(jìn)程的地址空間內(nèi)活動(dòng)。
2、資源分配給進(jìn)程,同一個(gè)進(jìn)程的所有線程共享該進(jìn)程所有資源。
3、CPU分配給線程,即真正在處理器運(yùn)行的是線程。
4、線程在執(zhí)行過(guò)程中需要協(xié)作同步,不同進(jìn)程的線程間要利用消息通信的辦法實(shí)現(xiàn)同步。
進(jìn)程與線程區(qū)別
1.同一個(gè)進(jìn)程中的線程共享同一內(nèi)存空間,但是進(jìn)程之間是獨(dú)立的。
2.同一個(gè)進(jìn)程中的所有線程的數(shù)據(jù)是共享的(進(jìn)程通訊),進(jìn)程之間的數(shù)據(jù)是獨(dú)立的。
3.對(duì)主線程的修改可能會(huì)影響其他線程的行為,但是父進(jìn)程的修改(除了刪除以外)不會(huì)影響其他子進(jìn)程。
4.線程是一個(gè)上下文的執(zhí)行指令,而進(jìn)程則是與運(yùn)算相關(guān)的一簇資源。
5.同一個(gè)進(jìn)程的線程之間可以直接通信,但是進(jìn)程之間的交流需要借助中間代理來(lái)實(shí)現(xiàn)。
6.創(chuàng)建新的線程很容易,但是創(chuàng)建新的進(jìn)程需要對(duì)父進(jìn)程做一次復(fù)制。
7.一個(gè)線程可以操作同一進(jìn)程的其他線程,但是進(jìn)程只能操作其子進(jìn)程。
8.線程啟動(dòng)速度快,進(jìn)程啟動(dòng)速度慢(但是兩者運(yùn)行速度沒(méi)有可比性
協(xié)程
對(duì)于 協(xié)程(用戶級(jí)線程),這是對(duì)內(nèi)核透明的,也就是系統(tǒng)并不知道有協(xié)程的存在,是完全由用戶自己的程序進(jìn)行調(diào)度的,因?yàn)槭怯捎脩舫绦蜃约嚎刂?,那么就很難像搶占式調(diào)度那樣做到強(qiáng)制的 CPU 控制權(quán)切換到其他進(jìn)程/線程,通常只能進(jìn)行 協(xié)作式調(diào)度,需要協(xié)程自己主動(dòng)把控制權(quán)轉(zhuǎn)讓出去之后,其他協(xié)程才能被執(zhí)行到。
本質(zhì)上,goroutine 就是協(xié)程。 不同的是,Golang 在 runtime、系統(tǒng)調(diào)用等多方面對(duì) goroutine 調(diào)度進(jìn)行了封裝和處理,當(dāng)遇到長(zhǎng)時(shí)間執(zhí)行或者進(jìn)行系統(tǒng)調(diào)用時(shí),會(huì)主動(dòng)把當(dāng)前 goroutine 的CPU (P) 轉(zhuǎn)讓出去,讓其他 goroutine 能被調(diào)度并執(zhí)行,也就是 Golang 從語(yǔ)言層面支持了協(xié)程。Golang 的一大特色就是從語(yǔ)言層面原生支持協(xié)程,在函數(shù)或者方法前面加 go關(guān)鍵字就可創(chuàng)建一個(gè)協(xié)程。
1. 內(nèi)存消耗方面
每個(gè) goroutine (協(xié)程) 默認(rèn)占用內(nèi)存遠(yuǎn)比 Java 、C 的線程少。
goroutine:2KB
線程:8MB
2. 線程和 goroutine 切換調(diào)度開銷方面
線程/goroutine 切換開銷方面,goroutine 遠(yuǎn)比線程小
線程:涉及模式切換(從用戶態(tài)切換到內(nèi)核態(tài))、16個(gè)寄存器、PC、SP...等寄存器的刷新等。
goroutine:只有三個(gè)寄存器的值修改 - PC / SP / DX.
go 協(xié)程實(shí)現(xiàn)原理
他和線程的原理是一樣的,當(dāng) a線程 切換到 b線程 的時(shí)候,需要將 a線程 的相關(guān)執(zhí)行進(jìn)度壓入棧,然后將 b線程 的執(zhí)行進(jìn)度出棧,進(jìn)入 b線程 的執(zhí)行序列。協(xié)程只不過(guò)是在 應(yīng)用層 實(shí)現(xiàn)這一點(diǎn)。但是,協(xié)程并不是由操作系統(tǒng)調(diào)度的,而且應(yīng)用程序也沒(méi)有能力和權(quán)限執(zhí)行 cpu 調(diào)度。怎么解決這個(gè)問(wèn)題?
答案是,協(xié)程是基于線程的。內(nèi)部實(shí)現(xiàn)上,維護(hù)了一組數(shù)據(jù)結(jié)構(gòu)和 n 個(gè)線程,真正的執(zhí)行還是線程,協(xié)程執(zhí)行的代碼被扔進(jìn)一個(gè)待執(zhí)行隊(duì)列中,由這 n 個(gè)線程從隊(duì)列中拉出來(lái)執(zhí)行。這就解決了協(xié)程的執(zhí)行問(wèn)題。那么協(xié)程是怎么切換的呢?答案是:golang 對(duì)各種 io函數(shù) 進(jìn)行了封裝,這些封裝的函數(shù)提供給應(yīng)用程序使用,而其內(nèi)部調(diào)用了操作系統(tǒng)的異步 io函數(shù),當(dāng)這些異步函數(shù)返回 busy 或 bloking 時(shí),golang 利用這個(gè)時(shí)機(jī)將現(xiàn)有的執(zhí)行序列壓棧,讓線程去拉另外一個(gè)協(xié)程的代碼來(lái)執(zhí)行,基本原理就是這樣,利用并封裝了操作系統(tǒng)的異步函數(shù)。包括 linux 的 epoll、select 和 windows 的 iocp、event 等。
由于golang是從編譯器和語(yǔ)言基礎(chǔ)庫(kù)多個(gè)層面對(duì)協(xié)程做了實(shí)現(xiàn),所以,golang的協(xié)程是目前各類有協(xié)程概念的語(yǔ)言中實(shí)現(xiàn)的最完整和成熟的。十萬(wàn)個(gè)協(xié)程同時(shí)運(yùn)行也毫無(wú)壓力。關(guān)鍵我們不會(huì)這么寫代碼。但是總體而言,程序員可以在編寫 golang 代碼的時(shí)候,可以更多的關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn),更少的在這些關(guān)鍵的基礎(chǔ)構(gòu)件上耗費(fèi)太多精力。
參考:
Golang 之協(xié)程詳解
https://www.liaoxuefeng.com/wiki/1016959663602400/1017627212385376
http://www.itdecent.cn/p/a4f5c1c041ae
Golang源碼探索(二) 協(xié)程的實(shí)現(xiàn)原理