在現代Linux操作系統中,一個程序在運行時,進程是分配資源的基本單位,Linux內核先fork一個子進程,分配物理內存,然後將要執行的可執行文件加載到內存。每個進程都是相互獨立的,進程之間如果需要通信則需要藉助第三方工具。
不同的進程在切換運行時,CPU需要不停地保存現場、恢復現場,因此進程上下切換的開銷是很大的。所以如果程序要並行執行很多任務,通過創建很多的進程來實現,不是個很好的主意。在現代linux操作系統中引入了線程,它的目的是為了減少進程的開銷,就是減少進程的切換。一個進程可能存在多個線程,但它至少有一個線程。多個線程之間的關係就像在一個家裏一樣,每個人都有自己的房間,但飯廳、客廳、衞生間都是公用的。多個線程都有各自獨立的資源,如程序計數器、寄存器上下文和各自的棧空間;但是進程中的代碼段、數據段、地址空間、打開的文件、信號處理程序等資源都是共享的。這些共享的資源為了保證程序的正確性符合邏輯,這共享資源會涉及安全訪問和線程間同步的問題,一般通過互斥鎖、條件鎖、讀寫鎖等鎖機制來實現對資源的安全訪問。這是很好理解的,就像在家裏的廁所有人正在使用,其他人就要等。
在C語言中,使用pthread庫來實現多線程對共享資源的互斥訪問:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化互斥鎖
int shared_num = 0; // 共享資源
void* thread_task(void* arg) {
pthread_mutex_lock(&mutex); // 加鎖
shared_num++;
printf(“共享變量值:%d\n”, shared_num);
pthread_mutex_unlock(&mutex); // 解鎖
pthread_exit(NULL); //退出程序
}
加鎖也會增加系統開銷,即當程序調用加鎖函數時,操作系統會從用户態切換到內核態,並阻塞在內核態;當程序調用解鎖時,也經歷從用户態切換到內核態,再從內核態切換回用户態。這告訴我們如果減少此類開鎖,就要減少加鎖、解鎖的操作。
我們再來説説線程池,雖然進程的多個線程可以共享進程的很多資源,如代碼段、數據段、打開的文件,線程們也有自己的上下文環境,如各自的寄存器狀態、棧、程序計數器等。在現代linux系統裏,進程是資源分配的基本單元,而線程則是程序執行和調試的最小單元。線程的開鎖除了線程上下文 切換帶來的開銷外,這裏還有另一個開銷,那就是線程的創建與銷燬,尤其是頻繁使用線程的場景下,這種開銷會特別明顯。
為了減少線程不斷創建與銷燬帶來的開銷,可以實現一個線程池。預先創建一些線程,沒有任務時,這些線程就阻塞在池中,有任務時,通過管理線程將任務分配到指定的線程來執行。
一般來説實現一個線程池需要有管理線程、工作線程和任務接口等組成。管理線程是用於創建和管理工作線程的,將用户創建的不同任務分配給不同的工作線程去執行;工作線程是真正做事的線程,它負責執行給過來的傷,沒有任務時,就阻塞中池中。線程池一般會提供一些任務接口供用户創建不同的任務,這些任務會分配到不同的線程中去執行。