linux 線程詳解

來源:互聯網
上載者:User

標籤:its   其他   var   之間   清單   基本   dead   如何   機制   

線程

是電腦中獨立啟動並執行最小單位,運行時佔用很少的系統資源。可以把線程看成是作業系統分配CPU時間的基本單元。一個進程可以擁有一個至多個線程。它線程在進程內部共用地址空間、開啟的檔案描述符等資源。同時線程也有其私人的資料資訊,包括:線程號、寄存器(程式計數器和堆棧指標)、堆棧、訊號掩碼、優先順序、線程私人儲存空間。

為什麼有了進程的概念後,還要再引入線程呢?使用多線程到底有哪些好處?什麼的系統應該選用多線程?我們首先必須回答這些問題。
  使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼片段、堆棧段和資料區段,這是一種"昂貴"的多任務工作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共用大部分資料,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。


  使用多線程的理由之二是線程間方便的通訊機制。對不同進程來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共用資料空間,所以一個線程的資料可以直接為其它線程所用,這不僅快捷,而且方便。當然,資料的共用也帶來其他一些問題,有的變數不能同時被兩個線程所修改,有的子程式中聲明為static的資料更有可能給多線程程式帶來災難性的打擊,這些正是編寫多線程程式時最需要注意的地方。


  除了以上所說的優點外,不和進程比較,多線程程式作為一種多任務、並發的工作方式,當然有以下的優點:
  1) 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、菜單的操作,而使用多線程技術,將耗時間長度的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。
  2) 使多CPU系統更加有效。作業系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
  3) 改善程式結構。一個既長又複雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程式會利於理解和修改。

建立線程

1 #include <pthread.h>
2 int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
3 pthread_t pthread_self(void);
4 int pthread_equal(pthread_t thread1,pthread_t thread2);
5 int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));

linux系統支援POSIX多線程介面,稱為pthread。編寫linux下的多線程程式,需要包含標頭檔pthread.h,連結時需要使用庫libpthread.a。

如果在主線程裡面建立線程,程式就會在建立線程的地方產生分支,變成兩個部分執行。線程的建立通過函數pthread_create來完成。成功返回0

參數:

thread: 參數是一個指標,當線程成功建立時,返回建立線程ID。

attr: 用於指定線程的屬性

start_routine: 該參數是一個函數指標,指向線程建立後要調用的函數。

arg: 傳遞給線程函數的參數。

一個簡單的建立線程程式:

$ cat main.c#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <pthread.h>int * thread(void *arg){        printf("thread id is %d.\n",pthread_self());        return NULL;}int main(){pthread_t id;printf("Main thread id is %d \n",pthread_self());if(!pthread_create(&id,NULL,(void *)thread,NULL)){printf("succeed!\n");return 0;}else{printf("Fail to Create Thread");return -1;}}
$ ./mainMain thread id is 1succeed!thread id is 2.
 
線程終止

兩種方式終止線程。

第一通過return從線程函數返回,

第二種通過調用pthread_exit()函數使線程退出。

需要注意的地方:一是,主線程中如果從main函數返回或是調用了exit函數退出主線程,則整個進程終止,此時所有的其他線程也將終止。另一種是,如果主線程調用pthread_exit函數,則僅僅是主線程消亡,進程不會結束,其他線程也不會結束,知道所有的線程都結束時,進程才結束。

線程屬性

1 /* man pthread_attr_init */
2 typedef struct
3 {
4   int                    detachstate;    //是否與其他線程脫離同步
5   int                    schedpolicy;    //新線程的調度策略
6   struct sched_param    schedparam;        //運行優先順序等
7   int                    inheritsched;    //是否繼承調用者線程的值
8   int                    scope;            //線程競爭CPU的範圍(優先順序的範圍)
9   size_t                guardsize;        //警戒堆棧的大小
10   int                    stackaddr_set;    //堆棧地址集
11   void *                stackaddr;        //堆棧地址
12   size_t                stacksize;        //堆棧大小
13 } pthread_attr_t;
屬性值不能直接設定,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。

關於線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為核心線程,它位於使用者層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。預設狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設定被綁定的輕進程的優先順序和調度級可以使得綁定的線程滿足諸如即時反應之類的要求。
  設定線程綁定狀態的函數為pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指標,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即建立了一個綁定的線程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化屬性值,均設為預設值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  線程的分離狀態決定一個線程以什麼樣的方式來終止自己。在上面的例子中,我們採用了線程的預設屬性,即為非分離狀態,這種情況下,原有的線程等待建立的線程結束。只有當pthread_join()函數返回時,建立的線程才算終止,才能釋放自己佔用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程式員應該根據自己的需要,選擇適當的分離狀態。設定線程分離狀態的函數為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這裡要注意的一點是,如果設定一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的線程裡調用pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設定一段等待時間,是在多線程編程裡常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。
  另外一個可能常用的屬性是線程的優先順序,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數pthread_attr_setschedparam進行存放,一般說來,我們總是先取優先順序,對取得的值修改後再存放回去。

線程等待——正確處理線程終止

1 #include <pthread.h>
2 void pthread_exit(void *retval);
3 void pthread_join(pthread_t th,void *thread_return);       //掛起等待th結束,*thread_return=retval;
4 int pthread_detach(pthread_t th);

      線程只能被一個線程等待終止(第一個能正常返回),並且應處於join狀態(非DETACHED)。

在 Linux 平台下,當處理線程結束時需要注意的一個問題就是如何讓一個線程善始善終,讓其所佔資源得到正確釋放。在 Linux 平台預設情況下,雖然各個線程之間是相互獨立的,一個線程的終止不會去通知或影響其他的線程。但是已經終止的線程的資源並不會隨著線程的終止而得到釋放,我們需要調用 pthread_join() 來獲得另一個線程的終止狀態並且釋放該線程所佔的資源。

調用該函數的線程將掛起,等待 th 所表示的線程的結束。 thread_return 是指向線程 th 傳回值的指標。需要注意的是 th 所表示的線程必須是 joinable 的,即處於非 detached(游離)狀態;並且只可以有唯一的一個線程對 th 調用 pthread_join() 。如果 th 處於 detached 狀態,那麼對 th 的 pthread_join() 調用將返回錯誤。

如果你壓根兒不關心一個線程的結束狀態,那麼也可以將一個線程設定為 detached 狀態,從而來讓作業系統在該線程結束時來回收它所佔的資源。將一個線程設定為 detached 狀態可以通過兩種方式來實現。一種是調用 pthread_detach() 函數,可以將線程 th 設定為 detached 狀態。其申明如清單 10 。

另一種方法是在建立線程時就將它設定為 detached 狀態,首先初始化一個線程屬性變數,然後將其設定為 detached 狀態,最後將它作為參數傳入線程建立函數 pthread_create(),這樣所建立出來的線程就直接處於 detached 狀態。方法如清單 11 。

     建立 detach 線程:

    pthread_t       tid; 
    pthread_attr_t  attr; 
    pthread_attr_init(&attr); 
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 
    pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

總之為了在使用 Pthread 時避免線程的資源線上程結束時不能得到正確釋放,從而避免產生潛在的記憶體流失問題,在對待線程結束時,要確保該線程處於 detached 狀態,否著就需要調用 pthread_join() 函數來對其進行資源回收。

線程私人資料

進程內的所有線程共用進程的資料空間,因此全域變數為所有線程所共有。但有時線程也需要儲存自己的私人資料,這時可以建立線程私人資料(Thread-specific Date)TSD來解決。線上程內部,私人資料可以被各個函數訪問,但對其他線程是屏蔽的。例如我們常見的變數errno,它返回標準的出錯資訊。它顯然不能是一個局部變數,幾乎每個函數都應該可以調用它;但它又不能是一個全域變數,否則在A線程裡輸出的很可能是B線程的出錯資訊。要實現諸如此類的變數,我們就必須使用線程資料。我們為每個線程資料建立一個鍵,它和這個鍵相關聯,在各個線程裡,都使用這個鍵來指代線程資料,但在不同的線程裡,這個鍵代表的資料是不同的,在同一個線程裡,它代表同樣的資料內容。

線程私人資料採用了一鍵多值的技術,即一個鍵對應多個數值,訪問資料時好像是對同一個變數進行訪問,但其實是在訪問不同的資料。

建立私人資料的函數有4個:pthread_key_create(建立), pthread_setspecific(設定), pthread_getspecific(擷取), pthread_key_delete(刪除)。

1 #include <pthread.h>
2 int pthread_key_creadte(pthread_key_t *key,void (*destr_fuction) (void *));
3 int pthread_setspecific(pthread_key_t key,const void * pointer));
4 void * pthread_getspecific(pthread_key_t key);
5 int pthread_key_delete(ptherad_key_t key);

線程同步

線程的最大特點是資源的共用性,但資源共用中的同步問題是多線程編程的痛點。linux下提供了多種方式來處理線程同步,最常用的是互斥鎖、條件變數和非同步訊號。

1)互斥鎖(mutex)

通過鎖機制實現線程間的同步。同一時刻只允許一個線程執行一個關鍵區段的代碼。

1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
2 int pthread_mutex_lock(pthread_mutex *mutex);
3 int pthread_mutex_destroy(pthread_mutex *mutex);
4 int pthread_mutex_unlock(pthread_mutex *      (1)先初始化鎖init()或靜態賦值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER      attr_t有:      PTHREAD_MUTEX_TIMED_NP:其餘線程等待隊列      PTHREAD_MUTEX_RECURSIVE_NP:嵌套鎖,允許線程多次加鎖,不同線程,解鎖後重新競爭      PTHREAD_MUTEX_ERRORCHECK_NP:檢錯,與一同,線程請求已用鎖,返回EDEADLK;      PTHREAD_MUTEX_ADAPTIVE_NP:適應鎖,解鎖後重新競爭      (2)加鎖,lock,trylock,lock阻塞等待鎖,trylock立即返回EBUSY      (3)解鎖,unlock需滿足是加鎖狀態,且由加鎖線程解鎖      (4)清除鎖,destroy(此時鎖必需unlock,否則返回EBUSY,//Linux下互斥鎖不佔用資源記憶體
$ cat main.c#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <pthread.h>pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;int a;int * thread(void *arg){        printf("thread id is %d.\n",pthread_self());        pthread_mutex_lock(&mutex);        a=10;        printf("a changed to %d.\n",a);        pthread_mutex_unlock(&mutex);        return NULL;}int main(){pthread_t id;printf("Main thread id is %d \n",pthread_self());a=3;printf("In main func a=%d\n",a);if(!pthread_create(&id,NULL,(void *)thread,NULL)){{printf("Create thread succeed!\n");}else{printf("Fail to Create Thread");return -1;}pthread_join(&id,NULL);pthread_mutex_destroy(&mutex);return 0;}

 

-bash-3.00$ ./mainMain thread id is 1In main func a=3Create thread succeed!-bash-3.00$
2)條件變數(cond)

利用線程間共用的全域變數進行同步的一種機制。

1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有線程的阻塞      (1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;屬性置為NULL      (2)等待條件成立.pthread_wait,pthread_timewait.wait()釋放鎖,並阻塞等待條件變數為真      timewait()設定等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)      (3)啟用條件變數:pthread_cond_signal,pthread_cond_broadcast(啟用所有等待線程)      (4)清除條件變數:destroy;無線程等待,否則返回EBUSY

 

對於int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond
pthread_mutex_t *mutex, const struct timespec *abstime);
一定要在mutex的鎖定地區內使用。

如果要正確的使用pthread_mutex_lock與pthread_mutex_unlock,請參考
pthread_cleanup_push和pthread_cleanup_pop宏,它能夠線上程被cancel的時候正確的釋放mutex!

另外,posix1標準說,pthread_cond_signal與pthread_cond_broadcast無需考慮調用線程是否是mutex的擁有者,也就是所,可以在lock與unlock以外的地區調用。如果我們對調用行為不關心,那麼請在lock地區之外調用吧。

  1 #include <stdio.h>
2 #include <pthread.h>

4 pthread_mutex_t mutex;
5 pthread_cond_t cond;
6 void *thread1(void *arg)
7 {
8     pthread_cleanup_push(pthread_mutex_unlock,&mutex);
9     //提供函數回調保護
10     while(1){
11         printf("thread1 is running\n");
12         pthread_mutex_lock(&mutex);
13         pthread_cond_wait(&cond,&mutex);
14         printf("thread1 applied the condition\n");
15         pthread_mutex_unlock(&mutex);
16         sleep(4);
17     }
18     pthread_cleanup_pop(0);
19 }
20 
21 void *thread2(void *arg)
22 {
23     while(1){
24         printf("thread2 is running\n");
25         pthread_mutex_lock(&mutex);
26         pthread_cond_wait(&cond,&mutex);
27         printf("thread2 applied the condition\n");
28         pthread_mutex_unlock(&mutex);
29         sleep(1);
30     }
31 }
32 int main()
33 {
34     pthread_t thid1,thid2;
35     printf("condition variable study!\n");
36     pthread_mutex_init(&mutex,NULL);
37     pthread_cond_init(&cond,NULL);
38     pthread_create(&thid1,NULL,(void*)thread1,NULL);
39     pthread_create(&thid2,NULL,(void*)thread2,NULL);
40     do{
41         pthread_cond_signal(&cond);
42     }while(1);
43     sleep(20);
44     pthread_exit(0);
45     return 0;

3)訊號量

如同進程一樣,線程也可以通過訊號量來實現通訊,雖然是輕量級的。

訊號量函數的名字都以"sem_"打頭。線程使用的基本訊號量函數有四個。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
這是對由sem指定的訊號量進行初始化,設定好它的共用選項(linux 只支援為0,即表示它是當前進程的局部訊號量),然後給它一個初始值VALUE。

兩個原子操作函數:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
這兩個函數都要用一個由sem_init調用初始化的訊號量對象的指標做參數。
sem_post:給訊號量的值加1;
sem_wait:給訊號量減1;對一個值為0的訊號量調用sem_wait,這個函數將會等待直到有其它線程使它不再是0為止。

int sem_destroy(sem_t *sem);
這個函數的作用是再我們用完訊號量後都它進行清理。歸還自己佔有的一切資源。

linux 線程詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.