一哄陶、并發(fā)性Concurrency
1帆阳、多任務(wù)
多任務(wù)是操作系統(tǒng)可以同時執(zhí)行多個任務(wù)。如屋吨,可以一邊聽音樂蜒谤,一邊刷微博,一邊聊QQ至扰,還能同時開微信鳍徽。這就是多任務(wù)同時運(yùn)行。
2敢课、線程process與進(jìn)程thread阶祭、協(xié)程coroutine
進(jìn)程是一個程序在一個數(shù)據(jù)集中的一次動態(tài)執(zhí)行過程绷杜,可以簡單理解為“正在執(zhí)行的程序”,它是CPU資源分配和調(diào)度的獨(dú)立單位濒募。
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位接剩。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位萨咳。一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程疫稿,每條線程并行執(zhí)行不同的任務(wù)培他。
簡單來說:
進(jìn)程:指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序;程序一旦運(yùn)行就是進(jìn)程遗座;進(jìn)程——資源分配的最小單位舀凛。
線程:系統(tǒng)分配處理器時間資源的基本單元,或者說進(jìn)程之內(nèi)獨(dú)立執(zhí)行的一個單元執(zhí)行流途蒋。線程——程序執(zhí)行的最小單位猛遍。
進(jìn)程的局限是創(chuàng)建、撤銷和切換的開銷比較大号坡。
線程的優(yōu)點(diǎn)是減小了程序并發(fā)執(zhí)行時的開銷懊烤,提高了操作系統(tǒng)的并發(fā)性能,缺點(diǎn)是線程沒有自己的系統(tǒng)資源宽堆,只擁有在運(yùn)行時必不可少的資源腌紧,但同一進(jìn)程的各線程可以共享進(jìn)程所擁有的系統(tǒng)資源。
操作系統(tǒng)的設(shè)計畜隶,因此可以歸結(jié)為三點(diǎn):
- (1)以多進(jìn)程形式壁肋,允許多個任務(wù)同時運(yùn)行;
- (2)以多線程形式籽慢,允許單個任務(wù)分成不同的部分運(yùn)行浸遗;
- (3)提供協(xié)調(diào)機(jī)制,一方面防止進(jìn)程之間和線程之間產(chǎn)生沖突箱亿,另一方面允許進(jìn)程之間和線程之間共享資源跛锌。
協(xié)程coroutine是一種用戶態(tài)的輕量級線程,又稱微線程届惋,協(xié)程的調(diào)度完全由用戶控制察净。人們通常將協(xié)程和子程序(函數(shù))比較著理解。 子程序調(diào)用總是一個入口盼樟,一次返回氢卡,一旦退出即完成了子程序的執(zhí)行。
與傳統(tǒng)的系統(tǒng)級線程和進(jìn)程相比晨缴,協(xié)程的最大優(yōu)勢在于其"輕量級"译秦,可以輕松創(chuàng)建上百萬個而不會導(dǎo)致系統(tǒng)資源衰竭,而線程和進(jìn)程通常最多也不能超過1萬的。這也是協(xié)程也叫輕量級線程的原因筑悴。
Go語言對于并發(fā)的實(shí)現(xiàn)是靠協(xié)程们拙,coroutine
3、并行與并發(fā)
并行與并發(fā)(Concurrency and Parallelism)是兩個不同的概念阁吝,理解它們對于理解多線程模型非常重要砚婆。
在描述程序的并發(fā)或者并行時,應(yīng)該說明從進(jìn)程或者線程的角度出發(fā)突勇。
- 并發(fā):一個時間段內(nèi)有很多的線程或進(jìn)程在執(zhí)行装盯,但任何時間點(diǎn)上都只有一個在執(zhí)行,多個線程或進(jìn)程爭搶時間片輪流執(zhí)行
- 并行:一個時間段和時間點(diǎn)上都有多個線程或進(jìn)程在執(zhí)行
并行需要硬件支持甲馋,單核處理器只能是并發(fā)埂奈,多核處理器才能做到并行執(zhí)行。
- 并發(fā)是并行的必要條件定躏,如果一個程序本身就不是并發(fā)的账磺,也就是只有一個邏輯執(zhí)行順序,那么我們不可能讓其被并行處理痊远。
- 并發(fā)不是并行的充分條件垮抗,一個并發(fā)的程序,如果只被一個CPU進(jìn)行處理(通過分時)碧聪,那么它就不是并行的借宵。
舉例:
如果我們在瀏覽web頁面時,下載文件和呈現(xiàn)頁面是周期交替執(zhí)行的矾削,這就是并發(fā)壤玫。
如果是運(yùn)行在多核CPU上,下載文件和呈現(xiàn)頁面可能同時在不同的內(nèi)核中運(yùn)行哼凯。這就是所謂的并行性欲间。
并行性Parallelism不會總是導(dǎo)致更快的執(zhí)行時間,因為并行運(yùn)行的組件可能需要相互通信断部,這種通信開銷很高猎贴,因此,并行程序并不總是導(dǎo)致更快的執(zhí)行時間!
二蝴光、幾種不同的多線程模型
線程的實(shí)現(xiàn)可以分為兩類:
- 用戶級線程(User-LevelThread, ULT):用戶線程由用戶代碼支持她渴。
- 內(nèi)核級線程(Kemel-LevelThread, KLT):內(nèi)核線程由操作系統(tǒng)內(nèi)核支持。
多線程模型:
多線程模型即用戶級線程和內(nèi)核級線程的不同連接方式蔑祟。
1趁耗、【用戶級線程模型】多對一模型(M : 1)
將多個用戶級線程映射到一個內(nèi)核級線程,線程管理在用戶空間完成疆虚。 此模式中苛败,用戶級線程對操作系統(tǒng)不可見(即透明)满葛。
優(yōu)點(diǎn): 這種模型的好處是線程上下文切換都發(fā)生在用戶空間,避免的模態(tài)切換(mode switch)罢屈,從而對于性能有積極的影響嘀韧。
缺點(diǎn):所有的線程基于一個內(nèi)核調(diào)度實(shí)體即內(nèi)核線程,這意味著只有一個處理器可以被利用缠捌,在多處理器環(huán)境下這是不能夠被接受的锄贷,本質(zhì)上,用戶線程只解決了并發(fā)問題曼月,但是沒有解決并行問題谊却。如果線程因為 I/O 操作陷入了內(nèi)核態(tài),內(nèi)核態(tài)線程阻塞等待 I/O 數(shù)據(jù)十嘿,則所有的線程都將會被阻塞,用戶空間也可以使用非阻塞而 I/O岳锁,但是不能避免性能及復(fù)雜度問題绩衷。
2、【內(nèi)核級線程模型】一對一模型(1 : 1)
將每個用戶級線程映射到一個內(nèi)核級線程激率。
每個線程由內(nèi)核調(diào)度器獨(dú)立的調(diào)度咳燕,所以如果一個線程阻塞則不影響其他的線程。
優(yōu)點(diǎn):在多核處理器的硬件的支持下乒躺,內(nèi)核空間線程模型支持了真正的并行招盲,當(dāng)一個線程被阻塞后,允許另一個線程繼續(xù)執(zhí)行嘉冒,所以并發(fā)能力較強(qiáng)曹货。
缺點(diǎn):每創(chuàng)建一個用戶級線程都需要創(chuàng)建一個內(nèi)核級線程與其對應(yīng),這樣創(chuàng)建線程的開銷比較大讳推,會影響到應(yīng)用程序的性能顶籽。
3、【兩級線程模型】多對多模型(M : N)
內(nèi)核線程和用戶線程的數(shù)量比為 M : N银觅,內(nèi)核用戶空間綜合了前兩種的優(yōu)點(diǎn)礼饱。
這種模型需要內(nèi)核線程調(diào)度器和用戶空間線程調(diào)度器相互操作,本質(zhì)上是多個線程被綁定到了多個內(nèi)核線程上究驴,這使得大部分的線程上下文切換都發(fā)生在用戶空間镊绪,而多個內(nèi)核線程又可以充分利用處理器資源。
三洒忧、goroutine機(jī)制的調(diào)度實(shí)現(xiàn)
goroutine機(jī)制實(shí)現(xiàn)了M : N的線程模型蝴韭,goroutine機(jī)制是協(xié)程(coroutine)的一種實(shí)現(xiàn),golang內(nèi)置的調(diào)度器熙侍,可以讓多核CPU中每個CPU執(zhí)行一個協(xié)程万皿。
理解goroutine機(jī)制的原理摧找,關(guān)鍵是理解Go語言調(diào)度器scheduler的實(shí)現(xiàn)。
1牢硅、調(diào)度器是如何工作的
Go語言中支撐整個scheduler實(shí)現(xiàn)的主要有4個重要結(jié)構(gòu)蹬耘,分別是M、G减余、P综苔、Sched, 前三個定義在runtime.h中位岔,Sched定義在proc.c中如筛。
Sched是調(diào)度器,它維護(hù)有存儲M和G的隊列以及調(diào)度器的一些狀態(tài)信息等抒抬。
M是Machine杨刨,系統(tǒng)線程,它由操作系統(tǒng)管理的擦剑,goroutine就是跑在M之上的妖胀;M是一個很大的結(jié)構(gòu),里面維護(hù)小對象內(nèi)存cache(mcache)惠勒、當(dāng)前執(zhí)行的goroutine赚抡、隨機(jī)數(shù)發(fā)生器等等非常多的信息。M的作用就是執(zhí)行G中包裝的并發(fā)任務(wù)纠屋。Go運(yùn)行時系統(tǒng)中的調(diào)度器的主要職責(zé)就是將G公平合理的安排到多個M上去執(zhí)行涂臣。
P是Processor,邏輯處理器售担,它的主要用途就是用來執(zhí)行g(shù)oroutine的赁遗,它維護(hù)了一個goroutine隊列,即runqueue族铆,并為G在M上的運(yùn)行提供本地化資源吼和。。Processor是讓我們從N:1調(diào)度到M:N調(diào)度的重要部分骑素。
G是goroutine炫乓,用go關(guān)鍵字加函數(shù)調(diào)用的代碼就是創(chuàng)建了一個G對象,是對一個要并發(fā)執(zhí)行的任務(wù)的封裝献丑,也可以稱作用戶態(tài)線程末捣。屬于用戶級資源,對OS透明创橄,具備輕量級箩做,可以大量創(chuàng)建,上下文切換成本低等特點(diǎn)妥畏。
Processor的數(shù)量是在啟動時被設(shè)置為環(huán)境變量GOMAXPROCS的值邦邦,或者通過運(yùn)行時調(diào)用函數(shù)GOMAXPROCS()進(jìn)行設(shè)置安吁。Processor數(shù)量固定意味著任意時刻只有GOMAXPROCS個線程在運(yùn)行g(shù)o代碼。
我們分別用三角形燃辖,矩形和圓形表示Machine Processor和Goroutine鬼店。
在單核處理器的場景下,所有g(shù)oroutine運(yùn)行在同一個M系統(tǒng)線程中黔龟,每一個M系統(tǒng)線程維護(hù)一個Processor妇智,任何時刻,一個Processor中只有一個goroutine氏身,其他goroutine在runqueue中等待巍棱。一個goroutine運(yùn)行完自己的時間片后,讓出上下文蛋欣,回到runqueue中航徙。 多核處理器的場景下,為了運(yùn)行g(shù)oroutines陷虎,每個M系統(tǒng)線程會持有一個Processor到踏。
在正常情況下,scheduler會按照上面的流程進(jìn)行調(diào)度泻红,但是線程會發(fā)生阻塞等情況夭禽,看一下goroutine對線程阻塞等的處理霞掺。
2谊路、線程阻塞
當(dāng)正在運(yùn)行的goroutine阻塞的時候,例如進(jìn)行系統(tǒng)調(diào)用菩彬,會再創(chuàng)建一個系統(tǒng)線程(M1)缠劝,當(dāng)前的M線程放棄了它的Processor,P轉(zhuǎn)到新的線程中去運(yùn)行骗灶。
3惨恭、runqueue執(zhí)行完成
當(dāng)其中一個Processor的runqueue為空,沒有g(shù)oroutine可以調(diào)度耙旦。它會從另外一個上下文偷取一半的goroutine脱羡。
Go運(yùn)行時系統(tǒng)通過構(gòu)造G-P-M對象模型實(shí)現(xiàn)了一套用戶態(tài)的并發(fā)調(diào)度系統(tǒng),可以自己管理和調(diào)度自己的并發(fā)任務(wù)免都,所以可以說Go語言原生支持并發(fā)锉罐。自己實(shí)現(xiàn)的調(diào)度器負(fù)責(zé)將并發(fā)任務(wù)分配到不同的內(nèi)核線程上運(yùn)行,然后內(nèi)核調(diào)度器接管內(nèi)核線程在CPU上的執(zhí)行與調(diào)度绕娘。
可以看到Go的并發(fā)用起來非常簡單脓规,用了一個語法糖將內(nèi)部復(fù)雜的實(shí)現(xiàn)結(jié)結(jié)實(shí)實(shí)的包裝了起來。其內(nèi)部可以用下面這張圖來概述:
參考文章:
https://zhuanlan.zhihu.com/p/77206570
https://www.cnblogs.com/williamjie/p/9456764.html