柚子快報(bào)邀請(qǐng)碼778899分享:Linux之線程控制
柚子快報(bào)邀請(qǐng)碼778899分享:Linux之線程控制
目錄
對(duì)頁表的再次理解(以32位為例)
線程的優(yōu)點(diǎn)
線程的缺點(diǎn)
線程異常
進(jìn)程VS線程
進(jìn)程ID和線程ID
線程控制
POSIX線程庫
創(chuàng)建線程
線程終止
線程等待
?分離線程
關(guān)于新線程退出結(jié)果
對(duì)頁表的再次理解(以32位為例)
并不是只有一個(gè)頁表, 地址的前10位對(duì)應(yīng)一級(jí)頁表,地址的第11-20位對(duì)應(yīng)二級(jí)頁表,后12位為頁內(nèi)偏移,其實(shí)內(nèi)存和磁盤中的文件,都被分成了以4KB為單位的區(qū)域,只不過磁盤中的4KB單元叫“頁幀”,內(nèi)存中的4KB單元叫“頁框”。
4KB = 2^12 Byte,即只要通過一級(jí)頁表和二級(jí)頁表找到內(nèi)存中對(duì)應(yīng)的頁框后,根據(jù)頁內(nèi)偏移就能找到對(duì)應(yīng)的資源。
線程在進(jìn)程內(nèi)部執(zhí)行,是OS調(diào)度的基本單位。
如何理解線程?
-------------------
上圖中,每一個(gè)task_struct就是一個(gè)線程,紅色方框內(nèi)是屬于一個(gè)進(jìn)程。
創(chuàng)建線程,不用構(gòu)建新的進(jìn)程地址空間,頁表,所以創(chuàng)建線程比創(chuàng)建進(jìn)程更輕量化。
創(chuàng)建線程,只需要利用主線程(原進(jìn)程)的地址空間,頁表等資源。
在CPU看來,并不關(guān)心執(zhí)行流是進(jìn)程還是線程,只認(rèn)task_struct,此時(shí),可以說Linux沒有真正意義上的線程結(jié)構(gòu),是用進(jìn)程task_struct模擬線程的,Linux下的進(jìn)程,統(tǒng)稱為:輕量級(jí)進(jìn)程!??!
-----------------------------------------------------------------------------------------------------------------------------
如何理解進(jìn)程?
--------------------
用戶視角:內(nèi)核數(shù)據(jù)結(jié)構(gòu)+對(duì)應(yīng)的代碼和數(shù)據(jù)!
內(nèi)核視角:承擔(dān)分配系統(tǒng)資源的實(shí)體!
----------------------------------------------------------------------------------------------------------------------------
線程的優(yōu)點(diǎn)
創(chuàng)建一個(gè)新線程的代價(jià)要比創(chuàng)建一個(gè)新進(jìn)程小得多;
與進(jìn)程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多;
線程占用的資源要比進(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
操作。
線程的缺點(diǎn)
性能損失
????????????????一個(gè)很少被外部事件阻塞的計(jì)算密集型線程往往無法與共它線程共享同一個(gè)處理器。如果計(jì)算密集型,線程的數(shù)量比可用的處理器多,那么可能會(huì)有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開銷,而可用的資源不變。
健壯性降低
????????????????編寫多線程需要更全面更深入的考慮,在一個(gè)多線程程序里,因時(shí)間分配上的細(xì)微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護(hù)的。
缺乏訪問控制
???????????進(jìn)程是訪問控制的基本粒度,在一個(gè)線程中調(diào)用某些OS函數(shù)會(huì)對(duì)整個(gè)進(jìn)程造成影響。
編程難度提高
????????????編寫與調(diào)試一個(gè)多線程程序比單線程程序困難得多
線程異常
因?yàn)閯?chuàng)建的新線程,與主線程共用地址空間,頁表等,所以新線程出現(xiàn)異常就會(huì)引起整個(gè)線程組異常。
單個(gè)線程如果出現(xiàn)除零,野指針問題導(dǎo)致線程崩潰,進(jìn)程也會(huì)隨著崩潰。
線程是進(jìn)程的執(zhí)行分支,線程出異常,就類似進(jìn)程出異常,進(jìn)而觸發(fā)信號(hào)機(jī)制,終止進(jìn)程,進(jìn)程終止,該進(jìn)程內(nèi)的所有線程也就隨即退出 。?
線程用途
合理的使用多線程,能提高
CPU
密集型程序的執(zhí)行效率
合理的使用多線程,能提高
IO
密集型程序的用戶體驗(yàn)(如生活中我們一邊寫代碼一邊下載開發(fā)工具,就是多線程運(yùn)行的一種表現(xiàn))
進(jìn)程VS線程
進(jìn)程是資源分配的基本單位;
線程是調(diào)度的基本單位;
線程共享進(jìn)程數(shù)據(jù),但也擁有自己的一部分?jǐn)?shù)據(jù):
線程
ID
一組寄存器
棧
errno
信號(hào)屏蔽字
調(diào)度優(yōu)先級(jí)
關(guān)于主線程和新線程對(duì)棧的使用
主線程和子線程用的棧不在同一個(gè)區(qū)域,主線程用的棧就在地址空間的棧區(qū),但是新線程用的棧區(qū)是在pthread庫中對(duì)應(yīng)的區(qū)域。
那么,怎么區(qū)分各個(gè)新線程的棧?
void *threadRun(void *args)
{
const string name = (char *)args;
while (true)
{
cout< sleep(1); } } int main() { pthread_t tid; pthread_create(&tid, nullptr, threadRun, (void *)"new thread"); while (true) { cout << "main thread, pid: " << getpid() << endl; sleep(1); } } 輸出結(jié)果:? 我們發(fā)現(xiàn),新線程的ID值非常大,我們在哪里見到過這么大的值那?--------虛擬地址!?。?/p> 其實(shí)不然,新線程的ID值,就是用來標(biāo)識(shí)該線程在pthread庫中對(duì)應(yīng)的存儲(chǔ)信息的起始位置,可以結(jié)合前邊的圖看到。 進(jìn)程的多個(gè)線程共享 同一地址空間,因此Text Segment、Data Segment都是共享的,如果定義一個(gè)函數(shù),在各線程中都可以調(diào)用,如果定義一個(gè)全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進(jìn)程資源和環(huán)境: 文件描述符表 每種信號(hào)的處理方式 (SIG_ IGN 、 SIG_ DFL 或者自定義的信號(hào)處理函數(shù) ) 當(dāng)前工作目錄 用戶 id 和組 id 主線程與新線程共用全局變量 void *threadRoutine(void *args) { pthread_detach(pthread_self()); while(true) { cout << (char*)args << " : " << g_val << " &: " << &g_val << endl; g_val++; sleep(1); } pthread_exit((void*)11); } int main() { pthread_t tid; pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1"); while(true) { cout << "main thread" << " : " << g_val << " &: " << &g_val << endl; sleep(1); } return 0; } 部分打印結(jié)果: (我們發(fā)現(xiàn),新線程修改全局變量,主線程的也會(huì)改變,而且地址是一樣的?。?/p> ? 如果用? __thread? 來修飾全局變量的話,會(huì)發(fā)現(xiàn)新線程修改全局變量,主線程看到的值并不會(huì)發(fā)生改變,而且二者對(duì)應(yīng)的地址不同。 在C++中,__thread是一個(gè)特定于某些編譯器(如GCC)的關(guān)鍵字,用于聲明線程局部存儲(chǔ)(Thread-Local Storage,TLS)的變量。這意味著每個(gè)線程都有其自己的該變量的副本,而不是所有線程共享同一個(gè)變量。 當(dāng)你在一個(gè)全局變量或靜態(tài)變量前使用__thread修飾符時(shí),你告訴編譯器這個(gè)變量是線程局部的。每個(gè)線程在訪問這個(gè)變量時(shí),實(shí)際上是在訪問它自己的私有副本,而不是共享的內(nèi)存區(qū)域。 進(jìn)程ID和線程ID struct task_struct { ????????... ????????pid_t pid; ????????pid_t tgid; ????????... ????????struct task_struct *group_leader; ????????... ????????struct list_head thread_group; ????????... }; 多線程的進(jìn)程,又被稱為線程組,線程組內(nèi)的每一個(gè)線程在內(nèi)核之中都存在一個(gè)進(jìn)程描述符( task_struct )與之對(duì)應(yīng)。進(jìn)程描述符結(jié)構(gòu)體中的pid ,表面上看對(duì)應(yīng)的是進(jìn)程 ID ,其實(shí)不然,它對(duì)應(yīng)的是線程 ID;進(jìn)程描述 符中的 tgid ,含義是 Thread Group ID, 該值對(duì)應(yīng)的是用戶層面的進(jìn)程 ID。 ?認(rèn)識(shí)進(jìn)程ID和線程ID void *threadRun(void *args) { const string name = (char *)args; while (true) {} } int main() { pthread_t tid[5]; for (int i = 0; i < 5; i++) { pthread_create(tid + i, nullptr, threadRun, (void *)""); } while (true) {} } ps 命令中的 -L 選項(xiàng),會(huì)顯示如下信息: LWP: 線程 ID ,既 gettid() 系統(tǒng)調(diào)用的返回值。 NLWP: 線程組內(nèi)線程的個(gè)數(shù) -f?選項(xiàng)表示使用完整格式輸出,包括UID, PID, PPID, C, STIME, TTY, TIME, CMD等字段。 ?其中,PID值和LWP值相同的是主線程,其余的都是新線程。 pthread_ create 函數(shù)會(huì)產(chǎn)生一個(gè)線程 ID ,存放在第一個(gè)參數(shù)指向的地址中。 因?yàn)榫€程是輕量級(jí)進(jìn)程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個(gè)數(shù)值來唯一表示該線程。 pthread_ create 函數(shù)第一個(gè)參數(shù)指向一個(gè)虛擬內(nèi)存單元,該內(nèi)存單元的地址即為新創(chuàng)建線程的線程 ID ,屬于NPTL線程庫的范疇。線程庫的后續(xù)操作,就是根據(jù)該線程 ID 來操作線程的。 線程庫 NPTL 提供了 pthread_ self 函數(shù),可以獲得線程自身的 ID : pthread_t pthread_self(void); 線程控制 POSIX線程庫 與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“pthread_”打頭的 要使用這些函數(shù)庫,要通過引入頭文 鏈接這些線程函數(shù)庫時(shí)要使用編譯器命令的“-lpthread”選項(xiàng) 創(chuàng)建線程 功能:創(chuàng)建一個(gè)新的線程 原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void*), void *arg); 參數(shù) ????????thread:返回線程 ID ????????attr:設(shè)置線程的屬性, attr 為 NULL 表示使用默認(rèn)屬性 ????????start_routine:是個(gè)函數(shù)地址,線程啟動(dòng)后要執(zhí)行的函數(shù) ????????arg:傳給線程啟動(dòng)函數(shù)的參數(shù) 返回值:成功返回 0 ;失敗返回錯(cuò)誤碼 線程終止 如果需要只終止某個(gè)線程而不終止整個(gè)進(jìn)程 , 可以有三種方法 : 1. 從線程函數(shù)return。這種方法對(duì)主線程不適用,從main函數(shù)return相當(dāng)于調(diào)用exit。 2. 線程可以調(diào)用pthread_ exit終止自己。 3. 一個(gè)線程可以調(diào)用pthread_ cancel終止同一進(jìn)程中的另一個(gè)線程。 ?pthread_exit函數(shù) 功能:線程終止 原型 void pthread_exit(void *value_ptr); 參數(shù) ????????value_ptr:value_ptr不要指向一個(gè)局部變量。 返回值:無返回值,跟進(jìn)程一樣,線程結(jié)束的時(shí)候無法返回到它的調(diào)用者(自身) pthread_cancel函數(shù) 功能:取消一個(gè)執(zhí)行中的線程 原型 int pthread_cancel(pthread_t thread); 參數(shù) ????????thread:線程 ID 返回值:成功返回 0 ;失敗返回錯(cuò)誤碼 線程等待 功能:等待線程結(jié)束 原型 int pthread_join(pthread_t thread, void **value_ptr); 參數(shù) ????????thread:線程 ID ????????value_ptr:它指向一個(gè)指針,后者指向線程的返回值 返回值:成功返回 0 ;失敗返回錯(cuò)誤碼 調(diào)用該函數(shù)的線程將掛起等待 , 直到 id 為 thread 的線程終止。 thread 線程以不同的方法終止 , 通過 pthread_join 得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下: 1. 如果thread線程通過return返回,value_ ptr所指向的單元里存放的是thread線程函數(shù)的返回值。 2. 如果thread線程被別的線程調(diào)用pthread_ cancel異常終掉,value_ ptr所指向的單元里存放的是常數(shù)PTHREAD_ CANCELED。 3. 如果thread線程是自己調(diào)用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數(shù)。 4. 如果對(duì)thread線程的終止?fàn)顟B(tài)不感興趣,可以傳NULL給value_ ptr參數(shù)。 為什么需要線程等待? 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進(jìn)程的地址空間內(nèi)。 創(chuàng)建新的線程不會(huì)復(fù)用剛才退出線程的地址空間。 ?分離線程 默認(rèn)情況下,新創(chuàng)建的線程是joinable的,線程退出后,需要對(duì)其進(jìn)行pthread_join操作,否則無法釋放資源,從而造成系統(tǒng)泄漏。 如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個(gè)時(shí)候,我們可以告訴系統(tǒng),當(dāng)線程退出時(shí),自動(dòng)釋放線程資源。 int pthread_detach(pthread_t thread); ? 可以是線程組內(nèi)其他線程對(duì)目標(biāo)線程進(jìn)行分離,也可以是線程自己分離 : pthread_detach(pthread_self()) ; 關(guān)于新線程退出結(jié)果 void *threadRoutine(void *args) { while(true) { pthread_testcancel(); } } int main() { pthread_t tid; pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1"); int count = 0; while (true) { count++; if (count >= 5) break; } int n= pthread_cancel(tid); cout < cout << "pthread cancel: " << tid << endl; int *ret = nullptr; pthread_join(tid, (void **)&ret); // 默認(rèn)會(huì)阻塞等待新線程退出 cout << "main thread wait done ... main quit ...: new thead quit : " << (long long)ret << "\n"; return 0; } 如果調(diào)用pthread_cancel來終止線程,則線程的退出碼是 -1。 當(dāng)然,如果新線程指定返回退出結(jié)果,可通過(void*)進(jìn)行強(qiáng)轉(zhuǎn),然后主線程再(long long)進(jìn)行強(qiáng)轉(zhuǎn),就可獲得。 柚子快報(bào)邀請(qǐng)碼778899分享:Linux之線程控制 好文閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。