一诀艰、線程的實(shí)現(xiàn)
線程的實(shí)現(xiàn)方式主要有三種:內(nèi)核線程實(shí)現(xiàn)柬甥、用戶線程實(shí)現(xiàn)、用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)其垄。因?yàn)樽约褐粚?duì)java的線程比較熟悉一點(diǎn)苛蒲,所以主要針對(duì)java線程和go的協(xié)程之間進(jìn)行一個(gè)對(duì)比。
線程模型主要有三種:1捉捅、內(nèi)核級(jí)別線程撤防;2虽风、用戶級(jí)別線程棒口;3寄月、混合線程
1、內(nèi)核級(jí)別線程
內(nèi)核級(jí)別線程就是直接由操作系統(tǒng)的內(nèi)核(kernel)支持的線程无牵,這種方式實(shí)現(xiàn)的線程主要通過內(nèi)核的調(diào)度器來進(jìn)行調(diào)度漾肮,由內(nèi)核完成線程切換。一般來講程序不會(huì)直接調(diào)用系統(tǒng)內(nèi)核線程茎毁,而是利用內(nèi)核線程的一種高級(jí)接口-輕量級(jí)進(jìn)程(Light Weight Process,即LWP克懊,它也可以視為用戶線程),也就是我們平時(shí)所說的線程七蜘,每一個(gè)LWP都是由一個(gè)內(nèi)核線程支持谭溉,也就是先有內(nèi)核線程,再有LWP橡卤。這種LWP與內(nèi)核線程之間1:1的關(guān)系稱為一對(duì)一線程模型扮念,這是一種最簡(jiǎn)單的線程實(shí)現(xiàn)方式。見下圖:
這種方式創(chuàng)建的每一個(gè)線程都需要由一個(gè)內(nèi)核線程支持碧库,需要消耗一定的內(nèi)核資源柜与,因此一個(gè)系統(tǒng)支持的線程數(shù)量是有限的。另外由于基于內(nèi)核線程實(shí)現(xiàn)嵌灰,這種方式創(chuàng)建的線程操作需要進(jìn)行系統(tǒng)調(diào)用弄匕,而系統(tǒng)調(diào)用代價(jià)較高,需要在用戶態(tài)和內(nèi)核態(tài)進(jìn)行切換(這個(gè)我也不懂)沽瞭。
2迁匠、用戶級(jí)別線程
這里所指的用戶級(jí)線程主要是創(chuàng)建在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核感受不到線程的實(shí)現(xiàn)方式驹溃。用戶線程的建立柒瓣、同步、銷毀等在用戶態(tài)中完成吠架,不需要內(nèi)核的介入芙贫。這種進(jìn)程和用戶線程(UT)之間1:N的關(guān)系稱為一對(duì)多線程模型。
這種方式的優(yōu)勢(shì)就是上下文切換比較快傍药,缺點(diǎn)是無(wú)法從多線程處理器或多處理器計(jì)算機(jī)上的硬件加速中受益磺平,同時(shí)調(diào)度的線程永遠(yuǎn)不會(huì)超過一個(gè)。
3拐辽、混合線程
這種方式相當(dāng)于是第一種方式和第二種方式的混合拣挪,即有LWT,也有用戶線程俱诸,這種方式中用戶線程(UT)和LWT的數(shù)量比是不定的菠劝,即所謂的N:M關(guān)系,也就是所謂的多對(duì)多模型睁搭。這種實(shí)現(xiàn)線程的方式相比前兩種也更為復(fù)雜赶诊,這種方式中由線程庫(kù)負(fù)責(zé)在可用的可調(diào)度實(shí)體上調(diào)度用戶線程笼平,這使得線程的上下文切換非常快舔痪,因?yàn)樗苊饬讼到y(tǒng)調(diào)用寓调。但是增加了復(fù)雜性和優(yōu)先級(jí)倒置的可能性,以及在用戶態(tài)調(diào)度程序和內(nèi)核調(diào)度程序之間沒有廣泛(且高昂)協(xié)調(diào)的次優(yōu)調(diào)度锄码。
java線程實(shí)現(xiàn)
主要說下常用的hotspot的JVM夺英,采用的是第一種1:1的線程模型,即:map a java thread to a native thread滋捶,也就是說java線程會(huì)和native線程有個(gè)一一映射的關(guān)系痛悯,如果看下java的Thread類就可以發(fā)現(xiàn)有很多的native方法,這就涉及到操作系統(tǒng)的線程了重窟。
二灸蟆、go語(yǔ)言并發(fā)模式
go語(yǔ)言支持兩種并發(fā)模式,一種是Communicating Sequential Processes(CSP)模式亲族,這種模式中值是在相互獨(dú)立的協(xié)程(goroutine)中傳遞的炒考,協(xié)程和協(xié)程之間使用到就是上次說到的channel。另外一種就是我們比較傳統(tǒng)的模式霎迫,也是我們相對(duì)熟悉的模式Share Memory Multithreading斋枢。但是go語(yǔ)言推薦的還是第一種模式,go官網(wǎng)文檔是說:Do not communicate by sharing memory; instead, share memory by communicating.也就是說不建議線程或協(xié)程之間通過共享內(nèi)存通訊知给,而是通過通訊共享內(nèi)存瓤帚。比方說比較熟悉的java其實(shí)就是共享內(nèi)存模式的并發(fā)模式,在涉及到多線程的問題時(shí)涩赢,必須考慮共享數(shù)據(jù)的安全性戈次。
三、線程和協(xié)程之間的區(qū)別
這里說的協(xié)程指的只是go語(yǔ)言的goroutine筒扒。線程和協(xié)程的區(qū)別主要是數(shù)量上的怯邪,而不是性質(zhì)上,所以說協(xié)程從邏輯上來說也是線程花墩。
棧的大行:
1、線程棧
操作系統(tǒng)的線程一般都分配有一塊固定大小的內(nèi)存塊(一般來說大小是2M冰蘑,這個(gè)需要查證)和泌,我查找資料顯示的64位Linux上,hotspot虛擬機(jī)的棧的大小默認(rèn)為1M祠肥,地址:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html武氓。。棧存儲(chǔ)的是方法的局部變量或者一些基本數(shù)據(jù)類型(java中)。因?yàn)闂5拇笮∈枪潭ǖ南厮。趫?zhí)行某些方法的時(shí)候可能就不太夠用东羹,比如一些比較復(fù)雜或則一些深的遞歸操作,比如熟悉的java有時(shí)候會(huì)有棧溢出異常弱睦;當(dāng)然某些時(shí)候2M可能顯得有點(diǎn)大百姓,這樣從一定程度上來說又造成了浪費(fèi)渊额。
2、協(xié)程
協(xié)程開始的時(shí)候也會(huì)分配一定大小的內(nèi)存區(qū)域旬迹,一般只有2K火惊,和線程的棧一樣,協(xié)程的棧存儲(chǔ)的也是局部變量奔垦,但是不同的是協(xié)程的棧的大小是不固定的屹耐,是可以根據(jù)需要自動(dòng)調(diào)整大小的,最大甚至可以達(dá)到1G椿猎,所以靈活性非常好惶岭。
調(diào)度方式
1、系統(tǒng)級(jí)別的線程是由操作系統(tǒng)的內(nèi)核(kernel)調(diào)度的犯眠,每過幾毫秒按灶,硬件的計(jì)時(shí)器就會(huì)中斷處理器,從而引起被成為調(diào)度器的內(nèi)核函數(shù)執(zhí)行筐咧。調(diào)度器會(huì)暫停當(dāng)前正在執(zhí)行的線程鸯旁,并把它的寄存器存到內(nèi)存中,并查看線程列表決定接下來執(zhí)行哪一個(gè)線程量蕊,然后從內(nèi)存中恢復(fù)改線程的寄存器铺罢,最后恢復(fù)該線程的執(zhí)行。因?yàn)榫€程是通過內(nèi)核調(diào)度的残炮,從一個(gè)線程切換到另一個(gè)線程就涉及到上下文轉(zhuǎn)換韭赘,建議看下維基百科:https://en.wikipedia.org/wiki/Context_switch。簡(jiǎn)單說就是將一個(gè)用戶線程的狀態(tài)保存到內(nèi)存势就,恢復(fù)另一個(gè)用戶線程的狀態(tài)辞居,并且更新調(diào)度程序的數(shù)據(jù)結(jié)構(gòu),導(dǎo)致上下文切換比較慢蛋勺。
2瓦灶、go語(yǔ)言使用了所謂的N:M調(diào)度的技術(shù)實(shí)現(xiàn)了自己的調(diào)度器,它在N個(gè)系統(tǒng)線程上多路復(fù)用(或調(diào)度)M個(gè)協(xié)程抱完,也就是說由n個(gè)系統(tǒng)線程贼陶,生成了m個(gè)go的協(xié)程(可以這么理解吧?)。go語(yǔ)言的調(diào)度工作類似于系統(tǒng)內(nèi)核的調(diào)度碉怔,但是它只關(guān)注單個(gè)go程序的協(xié)程烘贴。
另外一點(diǎn)就是go的協(xié)程是沒有標(biāo)識(shí)的,在java中當(dāng)前執(zhí)行的線程都會(huì)有一個(gè)唯一的標(biāo)識(shí)撮胧,它的好處就是可以很容易的就構(gòu)建出一個(gè)抽像的"thread-local storage"桨踪,即java中的ThreadLocal,每個(gè)線程都可以創(chuàng)建出這樣一個(gè)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)只屬于當(dāng)前線程的一些變量芹啥。但是goroutine并不支持锻离,因?yàn)門hreadLocal可能會(huì)被濫用。go語(yǔ)言提倡的是一種更簡(jiǎn)單的編程方式墓怀,即參數(shù)影響函數(shù)的行為應(yīng)該是顯性的汽纠。
go因?yàn)樵趧?chuàng)建協(xié)程的數(shù)量上一般沒有特別的限制,所以可以很輕松的創(chuàng)建出很多個(gè)協(xié)程出來傀履,而java因?yàn)椴捎玫氖?:1的線程模型虱朵,線程數(shù)量特別是并發(fā)線程數(shù)會(huì)受到CPU和操作系統(tǒng)的限制(我記得java線程池會(huì)獲取當(dāng)前可使用的CPU核數(shù),可能有誤)钓账,所以并發(fā)性能上應(yīng)該不如go語(yǔ)言碴犬,有人也說go語(yǔ)言天生就帶有高并發(fā)光環(huán)加持。這里無(wú)意區(qū)分java和go孰優(yōu)孰劣梆暮,只是想從線程和協(xié)程的實(shí)現(xiàn)上來簡(jiǎn)單的了解下二者的差別服协。
最后:自己在微信開了一個(gè)個(gè)人號(hào):
超超學(xué)堂
,都是自己之前寫過的一些文章惕蹄,另外關(guān)注還有Java免費(fèi)自學(xué)資料蚯涮,歡迎大家關(guān)注。