一、線程周邊問題
1.線程的優點
- 創建一個新線程的代價要比創建一個新進程小得多。
- 線程占用的資源要比進程少很多。
- 能充分利用多處理器的可并行數量。
- 在等待慢速I/O操作結束的同時,程序可執行其他的計算任務。
- 計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現。
- I/O密集型應用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
核心:
與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多:????????最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上下文切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能損耗是將寄存器中的內容切換出。????????另外?個隱藏的損耗是上下文的切換會擾亂處理器的緩存機制。簡單的說,?旦去切換上下文,處理器中所有已經緩存的內存地址?瞬間都作廢了。還有?個顯著的區別是當你改變虛擬內存空間的時候,處理的頁表緩沖 TLB (快表)會被全部刷新,這將導致內存的訪問在?段時間內相當的低效。但是在線程的切換中,不會出現這個問題,當然還有硬件cache。
2.線程的缺點
性能損失? ? ? ? 一個很少被外部事件阻塞的計算密集型線程往往無法與其它線程共享同?個處理器。如果計算密集型線程的數量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。健壯性降低????????編寫多線程需要更全面更深入的考慮,在一個多線程程序里,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。缺乏訪問控制????????進程是訪問控制的基本粒度,在一個線程中調用某些OS函數會對整個進程造成影響。編程難度提高????????編寫與調試?個多線程程序比單線程程序困難得多。
3.進程和線程對比
進程是資源分配的基本單位。線程是調度的基本單位。線程共享進程數據,但也擁有自己的一部分數據:????????線程ID? ? ? ? 一組寄存器,線程的上下文數據(核心)????????棧(核心)????????errno????????信號屏蔽字????????調度優先級棧說明線程有自己的入口函數,棧存放的是臨時變量,說明線程是一個動態的概念。一組寄存器說明線程是被獨立調度的。進程的多個線程共享:????????同?地址空間,因此Text Segment、Data Segment都是共享的,如果定義?個函數,在各線程中都可以調用,如果定義一個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:? ? ? ? 文件描述符表????????每種信號的處理方式(SIG_ IGN、SIG_ DFL或者自定義的信號處理函數)????????當前工作目錄? ? ? ? 用戶id和組id
二、線程控制
ps -aL可以用來查看用戶創建的線程。
task_struct
{
? ? ? ? pid_t pid;
? ? ? ? pid_t lwp;
}
light weight process:輕量級進程。CPU調度的時候,看lwp。
線程相關子問題:
1.關于調度的時間片問題:等分給不同的線程(防止線程無限分裂)
2.異常之后?任何一個線程崩潰,都會導致整個進程崩潰!
3.線程庫pthread是用戶層和操作系統層之間的一層中間層,在編譯時需要指定動態鏈接,帶上 -lpthread
????????Linux中,不存在真正意義上的線程,它所謂的概念,使用輕量級進程模擬的。在OS中,只有輕量級進程,所謂的線程,只是用戶層的概念。
????????但用戶層只認線程,因此pthread庫誕生了,把創建輕量級進程封裝起來,給用戶提供一批創建線程的接口。
?????????linux的線程實現,是在用戶層實現的。我們稱之為:用戶級線程。pthread為原生線程庫。
拓展:
linux創建輕量級進程的接口,vfork和clone創建子進程,和父進程共享地址空間。
C++11的多線程,在linux下,本質是封裝了pthread庫。
語言為了其跨平臺可移植性,其對應的線程實現都是封裝了各個操作系統的系統調用或對應的庫,通過條件編譯,形成庫。
int pthread_create(pthread_t *thread,? const pthread_attr_t *attr,
????????????????????????????????void *(*start_routine)(void *),? void *arg);
thread:線程id,輸出型參數(注:這個線程id是線程庫的概念,和lwp不同)
attr:線程屬性,設置稱nullptr
start_routine:新線程要執行的函數入口,一個函數指針
arg:新線程要執行的函數入口的參數
返回值:成功返回0,失敗返回錯誤數字。
注:線程創建好之后,新線程要被主線程等待->類似僵尸進程的問題,內存泄漏。
int pthread_join(pthread_t thread, void **retval);
thread:線程id
retval:返回值指針,輸出型參數
pthread_t pthread_self(void);? ? //獲取當前線程的id
為什么retval沒有信號部分的內容(沒有異常相關的內容)?
? ? 等待的目標線程,如果異常了,整個進程都退出了,包括main線程,所以,join異常,沒有意義,看也看不到!join都是基于:線程健康跑完的情況,不需要處理異常信號,異常信號,是進程要處理的話題!!!
1)線程的入口函數,進行return就是線程終止
注意:線程不能用exit終止,因為exit是終止進程的
2)pthread_exit()
3)如果線程被取消,退出結果是-1(PTHREAD_CANCLED)
pthread_exit函數功能:線程終?原型:void pthread_exit(void *value_ptr);參數:value_ptr:value_ptr不要指向?個局部變量。返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)????????需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的, 不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。pthread_cancel函數功能:取消?個執?中的線程原型:int pthread_cancel(pthread_t thread);參數:thread:線程ID返回值:成功返回0;失敗返回錯誤碼特別注意:取消的時候一定要保證新線程已經啟動。
2.線程分離
如果主線程不想再關心新線程,而是當新線程結束的時候,讓他自己釋放??
設置新線程為分離狀態
技術層面: ?線程默認是需要被等待的,joinable。如果不想讓主線程等待新線程
想讓新線程結束之后,自己退出,設置為分離狀態(!joinable or detach) ?// TODO
理解層面:線程分離,主分離新,新把自己分離。
分離的線程,依舊在進程的地址空間中,進程的所有資源,被分離的線程,依舊可以訪問,可以操作。主不等待新線程。
如果線程被設置為分離狀態,不需要進行join,join會失敗!!#include <iostream> #include <pthread.h> #include <string> #include <cstring> #include <unistd.h>void *routine(void *args) {std::string name = (char *)args;while (true){sleep(1);std::cout << "I am a thread, name: " << name << std::endl;}return (void *)123; }int main() {pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");// 分離新線程pthread_detach(tid);sleep(10);// 取消新線程pthread_cancel(tid);// 等待新線程,獲取返回值void *retval = nullptr;int ret = pthread_join(tid, &retval);if (ret != 0)std::cout << "等待失敗," << strerror(ret) << std::endl;elsestd::cout << "等待成功" << std::endl;while (true){std::cout << "當前是主線程,新線程已被回收,退出信息為:" << (long long int)retval << std::endl;sleep(1);}return 0; }
三、線程ID及地址空間布局
????????Linux中沒有真正意義上的線程 -> OS不會直接提供線程的接口 -> 在用戶層,封裝輕量級進程,形成原生線程庫 -> 動態庫,ELF格式 -> 加載到內存和映射到進程地址空間。
????????線程的概念是在庫中維護的,在庫內部,一定會事先存在一個或多個被創建好的線程,如何管理這些線程?先描述,在組織。
struct tcb
{
? ? ? ? // 線程應有的屬性
? ? ? ? // 線程狀態
? ? ? ? // 線程ID
? ? ? ? // 線程獨立的棧結構
? ? ? ? // 線程棧大小
? ? ? ? ... ...
};
????????為什么不包括上下文,lwp,優先級?因為這是linux系統內核的具體實現,不是操作系統學科對于線程的描述,用戶不需要關心具體實現。
????????進程自己的代碼區可以訪問到pthread庫內部的函數和數據。
線程ID本質:管理線程塊的起始虛擬地址。
線程傳參和返回值:保存在線程管理塊中
線程分離:線程管理塊中存在標識位,標識線程是否分離。
以下是 glibc-2.4 中 pthread 源碼相關內容:路徑: nptl/pthread_create.c
線程tcb結構屬性:
更正tid為lwp,輕量級進程的id。
創建線程create_thread