線程滿漢全席之線程、多線程概念和線程函數

目錄

線程概念篇:

線程(輕量級進程(LWP))概念:

多線程的概念:

標識:pid和tgid的區分

 進程與線程的區別:

多線程和多進程的區別:

 線程函數篇:

線程創建:

線程終止(線程退出):

線程等待:

線程分離:

 主線程退出的狀況


線程概念篇:

線程(輕量級進程(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時間,同時在一個進程內運行多個任務。

              但由於一個進程內多個線程共用一個虛擬地址空間,所以,一個執行流的異常會導致整個程序的退出,同時多線程還會導致程序健壯性低,代碼編寫複雜等問題。

 線程/多線程的優缺點

優點:

  1. 創建一個線程所用的資源要比進程小(新建線程僅需要保存一些自己能獨立執行程序的必要信息,新建進程會重新開闢一個新的進程虛擬地址空間)
  2. 進程當中的不同線程可以並行的運行,多線程序可以提高程序的運行效率

缺點:

  1. 健壯性/魯棒性低
  2. 多線程的程序當中有多個執行流,一旦一個執行流異常,會導致整個進程異常。
  3. 缺乏訪問控制
  4. 編程難度高,多個執行流可以併發的執行,併發執行的時候,可能會訪問同一個臨界資源,我們需要對訪問臨界資源的順序進行控制,防止程序產生二義性的結果。
  5. 性能損失:線程在調度的時候是有開銷的(上下文信息,程序計數器),如果大量的線程,會導致程序在頻繁的切換,佔用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;
}

注:如果本篇博客有任何錯誤和建議,歡迎夥伴們留言,你快說句話啊!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章