Linux線程概念
什么是線程?
- 在一個(gè)程序里的一個(gè)執(zhí)行路線叫做線程 thread ),更準(zhǔn)確的定義為:“線程是一個(gè)進(jìn)程內(nèi)部的控制序列"。
- 一切進(jìn)程至少有一個(gè)執(zhí)行線程。
- 線程在進(jìn)程內(nèi)部運(yùn)行,本質(zhì)上是在進(jìn)程地址空間中運(yùn)行。
- 在linux系統(tǒng)中,CPU看到的PCB比傳統(tǒng)的進(jìn)程更加輕量化。
- 透過(guò)進(jìn)程虛擬地址空間,可以看到進(jìn)程的大部分資源,將進(jìn)程資源合理分配給每個(gè)執(zhí)行流,就形成了線程執(zhí)行流。
我們?cè)谝郧八鶎W(xué)習(xí)的進(jìn)程知識(shí)中,一個(gè)進(jìn)程由進(jìn)程控制塊(task_struct),進(jìn)程地址空間( mm_struct ) ,頁(yè)表,頁(yè)表與進(jìn)程地址空間,物理內(nèi)存的映射為關(guān)系構(gòu)成。
每一個(gè)進(jìn)程都有自己獨(dú)有的進(jìn)程地址空間和頁(yè)表,對(duì)應(yīng)的映射關(guān)系,所以我們?cè)趧?chuàng)建進(jìn)程時(shí)需要耗費(fèi)大量的時(shí)間,空間。
但是,對(duì)于Linux系統(tǒng),如果我們?cè)趧?chuàng)建一個(gè)進(jìn)程后只創(chuàng)建task_struct,并且這些task_struct共享進(jìn)程地址空間,頁(yè)表等相關(guān)資源,圖示如下。
由于這些task_struct指向同一塊進(jìn)程地址空間和頁(yè)表,所以它們所看到的資源都是一樣的,我們可以讓這四個(gè)task_struct執(zhí)行不同的代碼區(qū)域(棧區(qū),堆區(qū)等以上區(qū)域),換句話說(shuō),我們之后創(chuàng)建出來(lái)的3個(gè)task_struct都可以同時(shí)執(zhí)行自己的代碼,從而完成”并發(fā)“。像這樣,我們把這樣的一份task_struct稱這為”線程“。
- 其中每一個(gè)線程都是當(dāng)前進(jìn)程里面的一個(gè)執(zhí)行流,也就是我們常說(shuō)的”線程是進(jìn)程內(nèi)部的一個(gè)執(zhí)行分支“。
- 線程在進(jìn)程內(nèi)部運(yùn)行,本質(zhì)就是線程在進(jìn)程地址空間內(nèi)運(yùn)行,也就是說(shuō)曾經(jīng)這個(gè)進(jìn)程申請(qǐng)的所有資源,幾乎都是被所有線程共享的。
- 線程比進(jìn)程粒度更細(xì),因?yàn)閳?zhí)行的代碼和數(shù)據(jù)也更小了。
- 線程調(diào)度的成本更低了,因?yàn)樗谡{(diào)度的時(shí)候,核心數(shù)據(jù)結(jié)構(gòu)(進(jìn)程地址空間和頁(yè)表都不用切換了)
windows下的線程和Linux下的線程區(qū)別
Linux其實(shí)并沒有真正對(duì)線程創(chuàng)建特定的數(shù)據(jù)結(jié)構(gòu)。
- 線程本身是在進(jìn)程內(nèi)部運(yùn)行的,操作系統(tǒng)中存在大量的進(jìn)程,一個(gè)進(jìn)程內(nèi)又存在一個(gè)或多個(gè)線程,因此線程的數(shù)量一定比進(jìn)程的數(shù)量多(線程 : 進(jìn)程 一定是n : 1),當(dāng)線程的數(shù)量足夠多的時(shí)候,很明顯線程的執(zhí)行粒度要比進(jìn)程更細(xì)。
- 對(duì)于這么多的線程我們OS需要對(duì)其做管理(先描述,再組織),在大部分的OS中,線程都有一個(gè)tcb。如果我們的系統(tǒng)實(shí)現(xiàn)的是真線程,比如說(shuō)windows平臺(tái),它就要分別對(duì)進(jìn)程和線程設(shè)計(jì)各自的描述的數(shù)據(jù)塊(結(jié)構(gòu)體),并且很多線程在一個(gè)進(jìn)程內(nèi)部,所以還要維護(hù)線程tcb和進(jìn)程pcb之間的關(guān)系。所以這樣寫出的代碼,其tcb和pcb兩個(gè)數(shù)據(jù)結(jié)構(gòu)之間的耦合度非常復(fù)雜。但是對(duì)于Linux來(lái)說(shuō),在概念上并沒有進(jìn)程和線程的區(qū)分,只將task_struct叫做一個(gè)執(zhí)行流,所以對(duì)于Linux來(lái)說(shuō),PCB和TCB為同一種。
Linux中使用pcb模擬tcb的優(yōu)勢(shì):
- 不需要單獨(dú)設(shè)計(jì)tcb了。
- 不用維護(hù)tcb和pcb之間的關(guān)系。
- 不用打再編寫調(diào)度算法了。
在Linux中,CPU是否能夠識(shí)別當(dāng)前調(diào)度的task_struct是進(jìn)程還是線程?
不能,因?yàn)镃PU只關(guān)心一個(gè)一個(gè)獨(dú)立的task_struct(執(zhí)行流),無(wú)論進(jìn)程內(nèi)部只有一個(gè)執(zhí)行流還是有多個(gè)執(zhí)行流,CPU都是以task_struct為單位進(jìn)行調(diào)度的。只是這里的task_struct只執(zhí)行一部分代碼和數(shù)據(jù),但也并不妨礙CPU執(zhí)行其他執(zhí)行流。
圖示如下:
理解修改常量區(qū)
字符串常量區(qū)在代碼區(qū)和已初始化數(shù)據(jù)區(qū)之間的,如果它不可被修改,那它是如何加載到物理內(nèi)存呢?如何保證它不可被修改的?
比如當(dāng)我們嘗試修改字符串,字符串常量區(qū)經(jīng)過(guò)頁(yè)表的映射到物理內(nèi)存,當(dāng)它從虛擬地址到物理地址轉(zhuǎn)換的時(shí)候,它是只讀的,所以RWX權(quán)限為R,所以嘗試在修改的時(shí)候直接在頁(yè)表進(jìn)行攔截,并結(jié)合MMU硬件轉(zhuǎn)換,識(shí)別到只讀但嘗試修改的異常,發(fā)出信號(hào),隨后OS把此進(jìn)程直接干掉。
如今,有了線程的引入,如何重新理解之前的進(jìn)程?
我們紅色方框框起來(lái)的內(nèi)容,我們把這個(gè)整體叫做進(jìn)程。
曾經(jīng)我們理解的進(jìn)程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進(jìn)程對(duì)應(yīng)的代碼和數(shù)據(jù),現(xiàn)在的進(jìn)程,站在內(nèi)核角度上看就是:承擔(dān)分配系統(tǒng)資源的基本實(shí)體。所有進(jìn)程最大的意義是向系統(tǒng)申請(qǐng)資源的基本單位。
因此,所謂的進(jìn)程并不是通過(guò)task_struct衡量的,還需要?jiǎng)?chuàng)建地址空間、維護(hù)頁(yè)表,然后在物理內(nèi)存當(dāng)中開辟空間、構(gòu)建映射,打開進(jìn)程默認(rèn)打開的相關(guān)文件、注冊(cè)信號(hào)對(duì)應(yīng)的處理方案等等。
我們之前接觸的進(jìn)程內(nèi)部只有一個(gè)task_struct,說(shuō)明該進(jìn)程內(nèi)部只有一個(gè)執(zhí)行流,也稱之為”單執(zhí)行流進(jìn)程“。
如果進(jìn)程內(nèi)部有多個(gè)執(zhí)行流,我們稱之為"多執(zhí)行流進(jìn)程"。
- Linux中,CPU實(shí)際上看到的task_struct實(shí)際上要比傳統(tǒng)的task_struct更加輕量化,當(dāng)進(jìn)程只有一個(gè)執(zhí)行流,那就說(shuō)明等于OS內(nèi)的進(jìn)程,但是如果進(jìn)程有多個(gè)執(zhí)行流,那就說(shuō)明該線程<其他OS內(nèi)傳統(tǒng)的PCB,CPU拿到的是進(jìn)程中多執(zhí)行流中某一個(gè)PCB,這某一個(gè)PCB并沒有單獨(dú)創(chuàng)建特定的數(shù)據(jù)結(jié)構(gòu),從宏觀上,所以Linux下的進(jìn)程統(tǒng)一稱之為: 輕量級(jí)進(jìn)程。
- 線程(一個(gè)執(zhí)行流)是CPU調(diào)度的基本單位。
原生線程庫(kù)pthread
在Linux中,站在內(nèi)核角度沒有真正意義上線程相關(guān)的接口,但是站在用戶角度,如果想創(chuàng)建線程,而不是只能用fork函數(shù),所以提供了pthread原生線程庫(kù)。
二級(jí)頁(yè)表
二級(jí)頁(yè)表引入
以32位平臺(tái)為例,在32個(gè)比特位中一共可以存在2^32個(gè)地址,如果由虛擬地址空間通過(guò)頁(yè)表映射到物理內(nèi)存中,那么一個(gè)頁(yè)表就需要2 ^32個(gè)表項(xiàng)存儲(chǔ)地址。
并且,每一個(gè)表項(xiàng)除了要保存的虛擬地址以及物理地址這里就大概需要8字節(jié),并且還需要保存一些權(quán)限信息,那么一個(gè)表項(xiàng)大概需要10個(gè)字節(jié),那么最后意味著存儲(chǔ)一張頁(yè)表OS需要2^32 * 10個(gè)字節(jié),那么也就是40GB。
可是,這遠(yuǎn)遠(yuǎn)超過(guò)了在32位平臺(tái)下4GB的內(nèi)存。
二級(jí)頁(yè)表
實(shí)際上,OS在64位平臺(tái)上是多級(jí)頁(yè)表,在32位平臺(tái)上是二級(jí)頁(yè)表。
這里以32位平臺(tái)為例,虛擬地址通過(guò)二級(jí)頁(yè)表映射關(guān)系如下:
- 選擇虛擬地址的前10個(gè)比特位在頁(yè)目錄中查找,通過(guò)映射關(guān)系找到對(duì)應(yīng)的頁(yè)表。
- 再選擇虛擬地址的10個(gè)比特位在對(duì)應(yīng)的頁(yè)表中查找,通過(guò)映射關(guān)系找到對(duì)應(yīng)的頁(yè)框的起始地址。
- 最后將虛擬地址的剩下12個(gè)比特位作為偏移量從對(duì)應(yīng)頁(yè)框的起始位置后進(jìn)行偏移,頁(yè)框起始地址 + 偏移量 = 物理內(nèi)存中對(duì)應(yīng)的字節(jié)數(shù)據(jù)。
說(shuō)明一下:
- 物理內(nèi)存是按照4KB單位進(jìn)行劃分的(每一個(gè)4KB單位叫做頁(yè)框),可執(zhí)行程序上也是被劃分為4KB為一個(gè)單位為頁(yè)幀,當(dāng)可執(zhí)行程序和加載到內(nèi)存中時(shí)也是以4KB進(jìn)行加載和保存的。
- 如果一級(jí)頁(yè)表有一張,那就說(shuō)明有2^10個(gè)表項(xiàng),進(jìn)而表示有2 ^ 10個(gè)二級(jí)頁(yè)表,并且1個(gè)表項(xiàng) 大概10個(gè)字節(jié),那么總字節(jié)數(shù)便為2 ^ 10 * 10個(gè)字節(jié),也就是10MB,這極大節(jié)省了空間。
- 上面所述的映射過(guò)程,總共由頁(yè)表和MMU硬件完成,其中頁(yè)表為軟件映射,MMU是一種硬件映射,OS由虛擬地址轉(zhuǎn)換為物理地址采用的是軟硬鏈接結(jié)合的方式。
上述頁(yè)表的優(yōu)勢(shì):
- 進(jìn)程虛擬地址管理和內(nèi)存管理,通過(guò)頁(yè)表 + page進(jìn)行了解耦。
- 頁(yè)表分離了,可以實(shí)現(xiàn)頁(yè)表的按需獲取,沒有用到的就不創(chuàng)建,進(jìn)而節(jié)省了空間。
線程的優(yōu)點(diǎn)
- 創(chuàng)建一個(gè)新線程的代價(jià)要比創(chuàng)建一個(gè)新進(jìn)程小得多。
- 與進(jìn)程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多,這里有兩個(gè)核心原因:
- 線程切換是不需要切換頁(yè)表和進(jìn)程地址空間的,而進(jìn)程與進(jìn)程調(diào)度之間需要切換頁(yè)表,進(jìn)程地址空間
- CPU內(nèi)部具有硬件L1~L3 cache緩存,在進(jìn)程進(jìn)行訪問(wèn)的時(shí)候,CPU提前就將目標(biāo)代碼和數(shù)據(jù)加載到CPU的緩沖區(qū)中,一個(gè)進(jìn)程內(nèi)部的執(zhí)行流訪問(wèn)時(shí)就可以可以直接從緩沖區(qū)中讀取,提高調(diào)度效率。但是如果進(jìn)程間切換,因?yàn)檫M(jìn)程具有獨(dú)立性,cache立即失效,新進(jìn)程執(zhí)行的時(shí)候,只能重新對(duì)該進(jìn)程預(yù)計(jì)執(zhí)行的代碼數(shù)據(jù)緩存。
- 線程占用的資源要比進(jìn)程少很多。
- 能充分利用多處理器的可并行數(shù)量。
- 在等待慢速I/O操作結(jié)束的同時(shí),程序可執(zhí)行其他的計(jì)算任務(wù)。
- 計(jì)算密集型應(yīng)用,為了能在多處理器系統(tǒng)上運(yùn)行,將計(jì)算分解到多個(gè)線程中實(shí)現(xiàn)。
- I/O密集型應(yīng)用,為了提高性能,將I/O操作重疊。線程可以同時(shí)等待不同的I/O操作。
那么,一個(gè)進(jìn)程中線程是不是越多越好?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-708621.html
不是,即便線程切換搜耗費(fèi)的成本較低。但是如果線程過(guò)多,那么會(huì)造成線程之間的調(diào)度成本過(guò)大。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-708621.html
線程的缺點(diǎn)
- 性能損失
- 一個(gè)很少被外部事件阻塞的計(jì)算密集型線程往往無(wú)法與共它線程共享同一個(gè)處理器。如果計(jì)算密集型線程的數(shù)量比可用的處理器多,那么可能會(huì)有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開銷,而可用的資源不變。
- 健壯性降低
- 編寫多線程需要更全面更深入的考慮,在一個(gè)多線程程序里,因時(shí)間分配上的細(xì)微偏差或者因共享了。
- 不該共享的變量而造成不良影響的可能性是很大的,換句話說(shuō)線程之間是缺乏保護(hù)的。
- 缺乏訪問(wèn)控制
- 進(jìn)程是訪問(wèn)控制的基本粒度,在一個(gè)線程中調(diào)用某些OS函數(shù)會(huì)對(duì)整個(gè)進(jìn)程造成影響(一個(gè)線程可能會(huì)影響到其他線程運(yùn)行)。
- 編程難度提高
編寫與調(diào)試一個(gè)多線程程序比單線程程序困難得多。
線程異常
- 單個(gè)線程如果出現(xiàn)除零,野指針問(wèn)題導(dǎo)致線程崩潰,進(jìn)程也會(huì)隨著崩潰,因?yàn)樵贚inux中線程就是進(jìn)程的一部分。
- 線程是進(jìn)程的執(zhí)行分支,線程出異常,就類似進(jìn)程出異常,進(jìn)而觸發(fā)信號(hào)機(jī)制,終止進(jìn)程,進(jìn)程終止,該進(jìn)程內(nèi)的所有線程也就隨即退出。
到了這里,關(guān)于Linux 多線程 ( 多線程概念 )的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!