目錄
線程概念篇:
線程(輕量級進程(LWP))概念:
線程就是一個執行流,創建一個線程就相當於在內核當中創建一個PCB(task_struct),創建出來的PCB當中的內存指針是指向進程的虛擬地址空間的。
但我們存在一個疑問,創建子進程我們知道一個函數vfork,子進程拷貝父進程的PCB,這樣子進程可執行父進程的進程虛擬地址空間,這樣就存在一個大的問題:調用棧混亂;這也是我們不常用vfork函數的原因,倘若用vfork創建子進程的時候,我們必須讓父進程進行進程等待,以避免產生調用棧混亂的問題,這樣做和單進程效率一樣,且浪費資源。
而我們如果創建一個線程,同樣會出現剛纔的情況,線程的PCB的內存指針指向進程的虛擬地址空間,這會不會帶來vfork函數相同的問題?調用棧混亂
我們創建多線程的時候,其實並不會和vfork創建多進程一樣,出現調用棧混亂的問題!
原因:我們在創建多線程的時候,創建一個線程就等於在內核中創建了PCB,而PCB的內存指針指向進程的虛擬地址空間,如果僅是如此,那肯定會造成調用棧混亂的問題!但是創建線程後,會在共享區開闢一段空間,保存着該線程自己獨有的東西,其中就含有調用棧,如此一來,就避免了調用棧混亂的問題。
那在共享區開闢的空間上都保留着那些東西呢?
這就涉及了多線程間共享和獨有的問題了。
共享:進程中所有的信息對該進程內的線程都是共享的,eg:進程虛擬地址空間,文件描述符表、信號的處理方式、進程當前的工作路徑、用戶ID、用戶組ID、程序的全局內存、堆內存等等
獨有:進程內執行環境所必須的信息,eg:線程ID、棧、errno變量、信號屏蔽字、調度優先級及策略、一組寄存器、線程的私有數據等等
多線程的概念:
由於一個線程就是一個執行流,那麼多個線程就是多個執行流,在多核CPU的機器當中,同一時間,每一個執行流理論上都可以擁有一個CPU,然後並行的去運行,多線程可以提高運行的效率。
線程的概念是c庫當中的概念,因爲線程的接口都是c庫提供的。底層調用clone接口
同時,承接上面的調用棧混亂的問題:
爲了避免多線程出現調用棧混亂的問題,每創建一個子線程,就會在共享區開闢一段空間,保存該線程所獨有的東西
標識:pid和tgid的區分
我們在PCB中即tack_struct結構體中看到兩個變量:
pid_t pid;
pid_t tgid;
兩個變量都是pid_t類型,他們有什麼區別呢?
tgid:(thread group ID) 線程組id, 進程號,即進程內的主線程ID
pid :(process ID) 線程ID,即我們創建的子線程ID
進程與線程的區別:
一個進程可以包含若干線程,使用線程可以實現應用程序同時做幾件事並且互相不干擾。
進程爲應用程序的運行實例,是應用程序的一次動態執行,進程是操作系統進行資源分配的單位。
線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源。
線程與同屬一個進程的其他線程共享進程所擁有的全部資源。一個線程可以創建和撤銷另一個線程,同一進程中的多個線程之間可以併發執行。
多線程和多進程的區別:
多進程:每一個進程都用自己獨立的虛擬地址空間,這個也是進程獨立性的原因。一個進程的崩潰,不會導致另外一個進程受到影響。
多進程的程序也可以提高程序運行效率(本質也是增加執行流),但是帶來了進程間通信的問題。
多線程:多個線程則共享進程的數據空間,每個線程有自己的執行堆棧和程序計數器爲其執行上下文。多線程主要是爲了利用CPU時間,同時在一個進程內運行多個任務。
但由於一個進程內多個線程共用一個虛擬地址空間,所以,一個執行流的異常會導致整個程序的退出,同時多線程還會導致程序健壯性低,代碼編寫複雜等問題。
線程/多線程的優缺點
優點:
- 創建一個線程所用的資源要比進程小(新建線程僅需要保存一些自己能獨立執行程序的必要信息,新建進程會重新開闢一個新的進程虛擬地址空間)
- 進程當中的不同線程可以並行的運行,多線程序可以提高程序的運行效率
- 健壯性/魯棒性低
- 多線程的程序當中有多個執行流,一旦一個執行流異常,會導致整個進程異常。
- 缺乏訪問控制
- 編程難度高,多個執行流可以併發的執行,併發執行的時候,可能會訪問同一個臨界資源,我們需要對訪問臨界資源的順序進行控制,防止程序產生二義性的結果。
- 性能損失:線程在調度的時候是有開銷的(上下文信息,程序計數器),如果大量的線程,會導致程序在頻繁的切換,佔用CPU去執行
線程函數篇:
前言:線程函數都是庫函數,使用線程函數的時候我們需要鏈接線程庫即增加 -lpthread 選項
線程創建:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
參數:
thread:線程標識符,幫助我們標識一個線程,其實是一個出參,還記得線程創建的時候,要在共享區開闢弊端空間保存該線程獨有的信息嘛,該標識符其實是這段空間的首地址
attr:線程屬性 eg:該線程的棧大小,棧的其實位置,調度優先級、分離屬性等等
ulimit -s 命令可以查看棧大小
如果我們傳遞一個nullptr空指針,線程創建的時候將會採用默認屬性
start_routine:函數指針類型,創建的線程的入口函數,即新建線程要處理的任務函數
arg:傳遞給線程入口函數的參數,注意:線程入口函數只能傳遞一個參數,如果你要傳遞多個參數,可以考慮自己定義一個結構體,將結構體地址作爲arg進行傳遞
返回值:
線程創建成功,將會返回0
線程創建失敗,將會返回錯誤碼
注意arg這個參數,
因爲這個參數是傳遞給新建線程的入口函數的參數,我們要確保這個參數可以被新創建的線程所訪問到;
如果我們傳遞一個臨時變量,一定不可以,臨時變量存放在主線程的棧上,而我們新創建的線程擁有自己的棧,無法訪問主線程的棧,也就無法拿取那個臨時變量。
而全局變量是可以的,因爲全局變量存放在數據段,數據段是進程內線程共享的;堆也是進程內線程所共享的,我們也可以動態開闢一段空間,傳遞給新建線程,但注意及時釋放這段空間,防止內存泄漏
注意:
同進程創建一樣,線程創建時,並不能保證鬧個線程先被執行:新建的線程,主線程都有可能先執行
新建的線程會繼承調用線程的信號屏蔽字,但是未決信號集將會被清除
線程終止(線程退出):
線程退出的三種方法:
- return返回,退出
- 線程調用pthread_exit()函數
- 同一進程內的其他線程可以取消該線程
void pthread_exit(void *retval);
哪個線程調用這個函數,那個線程就會退出;
int pthread_cancel(pthread_t thread);
我們只需要傳入一個線程標識符,就可以讓該線程標識符表示的線程退出
參數:thread:想終止的線程的標識符
返回值:成功返回0;
失敗返回錯誤碼
線程等待:
int pthread_join(pthread_t thread, void **retval);
參數:thread:要等待的線程的線程標識符
retval:一個二級指針,出參,用於獲取線程的退出狀態
對應線程退出的三種方法:
- return退出;retval保存的內容是return返回的值
- pthread_exit(void*)退出;retval保存該函數的參數
- pthread_cancel(pthread_t);* retval中保存一個常數:PTHREAD_CANCELED
如果我們不關心退出狀態,可以傳遞一個nullptr空指針
pthread_join(pthread_t,void**)是一個阻塞接口,直至等待指定的線程退出
等待接口的作用:
線程在默認創建的時候,默認屬性當中被認爲joinable的
joinable:當線程退出的時候,需要其他線程來回收該線程的資源,如果沒有線程回收,則在共享區中,對於退出線程的空間還是保留的,退出線程的資源沒有被釋放,也就造成了內存泄漏
所以我們調用線程等待接口,可以使指定的線程爲分離狀態,釋放退出線程的資源,防止內存泄漏
如果我們不想進行線程等待,可使用線程分離接口,使線程爲分離狀態
線程分離:
改變線程的joinable屬性,變成detach,從而在線程退出的時候,線程在共享區開闢的資源直接被系統回收,不需要其他線程來回收該線程的資源
int pthread_detach(pthread_t thread);
參數:
thread:線程標識符
返回值:
成功返回0;
失敗返回錯誤碼
主線程退出的狀況
我們都知道每個進程中必定有一個主線程,倘若我們調用線程終止接口,是主線程退出,那麼整個進程會退出嗎?
只要有工作線程程,進程是不會退出的:(主線程類似進入Z狀態,稱爲所謂的“殭屍線程”)
如下面代碼所示:主線程退出之後,整個進程不會退出,工作線程狀態在R/S之間來回切換
void* start(void* arg)
{
while(1)
{
printf("I am working\n");
}
}
int main()
{
pthread_t tid;
int ret=pthread_create(&tid,NULL,start,NULL);
if(ret!=0)
{
perror("pthread_create");
return -1;
}
pthread_exit(NULL);
return 0;
}
如果沒有工作線程,那麼整個進程就會退出
void* start(void* arg)
{
// while(1)
{
sleep(1);
printf("I am working\n");
}
}
int main()
{
pthread_t tid;
int ret=pthread_create(&tid,NULL,start,NULL);
if(ret!=0)
{
perror("pthread_create");
return -1;
}
sleep(3);
pthread_exit(NULL);
return 0;
}
注:如果本篇博客有任何錯誤和建議,歡迎夥伴們留言,你快說句話啊!