Linux中執行緒使用詳解
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
Linux下多執行緒詳解pdf文件下載:點選這裡!
Linux中執行緒和程序的區別:http://blog.csdn.net/qq_21792169/article/details/50437304
執行緒退出的條件:下面任意一個都可以。
1.呼叫pthread_exit函式退出。
2.其他執行緒呼叫pthread_cancel取消該執行緒,且該執行緒可被取消。
3.建立執行緒的程序退出或者整個函式結束。
4.當前執行緒程式碼執行完畢。
5.其中的一個執行緒執行exec類函式執行新的程式碼,替換當前程序所有地址空間。
當執行緒中休眠或者死迴圈時候,需要在住程序中呼叫pthread_join等待執行緒結束,死迴圈可以通過另外一個休眠的執行緒來結束,舉例說明,讓LCD顯示攝像頭資料,但是我們中途需要點選觸控式螢幕來退出顯示,視訊顯示是一個死迴圈來不停的讀取視訊資料,那麼我們就可以建立兩個執行緒,一個負責視訊的不停讀取,一個負責獲取觸控式螢幕資料,沒有資料就休眠,當休眠被喚醒後就呼叫pthread_cancel取消死迴圈的執行緒,設計思路基本是這樣。也可以採取程序實現這個操作。
執行緒與程序
為什麼有了程序的概念後,還要再引入執行緒呢?使用多執行緒到底有哪些好處?什麼的系統應該選用多執行緒?我們首先必須回答這些問題。
使用多執行緒的理由之一是和程序相比,它是一種非常"節儉"的多工操作方式。我們知道,在Linux系統下,啟動一個新的程序必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼段、堆疊段和資料段,這是一種"昂貴"的多工工作方式。而運行於一個程序中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料,啟動一個執行緒所花費的空間遠遠小於啟動一個程序所花費的空間,而且,執行緒間彼此切換所需的時間也遠遠小於程序間切換所需要的時間。據統計,總的說來,一個程序的開銷大約是一個執行緒開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。
使用多執行緒的理由之二是執行緒間方便的通訊機制。對不同程序來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。執行緒則不然,由於同一程序下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用,這不僅快捷,而且方便。當然,資料的共享也帶來其他一些問題,有的變數不能同時被兩個執行緒所修改,有的子程式中宣告為static的資料更有可能給多執行緒程式帶來災難性的打擊,這些正是編寫多執行緒程式時最需要注意的地方。
除了以上所說的優點外,不和程序比較,多執行緒程式作為一種多工、併發的工作方式,當然有以下的優點:
1) 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、選單的操作,而使用多執行緒技術,將耗時長的操作(time consuming)置於一個新的執行緒,可以避免這種尷尬的情況。
2) 使多CPU系統更加有效。作業系統會保證當執行緒數不大於CPU數目時,不同的執行緒運行於不同的CPU上。
3) 改善程式結構。一個既長又複雜的程序可以考慮分為多個執行緒,成為幾個獨立或半獨立的執行部分,這樣的程式會利於理解和修改。
一、執行緒標識
- 執行緒有ID, 但不是系統唯一, 而是程序環境中唯一有效.
- 執行緒的控制代碼是pthread_t型別, 該型別不能作為整數處理, 而是一個結構.
下面介紹兩個函式:
- 標頭檔案: <pthread.h>
- 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
- 返回值: 相等返回非0, 不相等返回0.
- 說明: 比較兩個執行緒ID是否相等.
- 標頭檔案: <pthread.h>
- 原型: pthread_t pthread_self();
- 返回值: 返回呼叫執行緒的執行緒ID.
二、執行緒建立
在執行中建立一個執行緒, 可以為該執行緒分配它需要做的工作(執行緒執行函式), 該執行緒共享程序的資源. 建立執行緒的函式pthread_create()
- 標頭檔案: <pthread.h>
- 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
- 返回值: 成功則返回0, 否則返回錯誤編號.
- 引數:
- tidp: 指向新建立執行緒ID的變數, 作為函式的輸出.
- attr: 用於定製各種不同的執行緒屬性, NULL為預設屬性(見下).
- start_rtn: 函式指標, 為執行緒開始執行的函式名.該函式可以返回一個void *型別的返回值,而這個返回值也可以是其他型別,並由 pthread_join()獲取
- arg: 函式的唯一無型別(void)指標引數, 如要傳多個引數, 可以用結構封裝.
linux下多執行緒程式的編譯方法:
因為pthread的庫不是linux系統的庫,所以在進行編譯的時候要加上 -lpthread
# gcc filename -lpthread //預設情況下gcc使用c庫,要使用額外的庫要這樣選擇使用的庫
例:thread_create.c
#include <stdio.h>
#include <pthread.h> //包執行緒要包含
void *mythread1(void)
{
int i;
for(i=0;i<100;i++)
{
printf("this is the 1st pthread,created by zieckey.\n");
sleep(1);
}
}
void *mythread2(void)
{
int i;
for(i=0;i<100;i++)
{
printf("this is the 2st pthread,created by zieckey.\n");
sleep(1);
}
}
int main()
{
int ret=0;
pthread_tid1,id2;
ret=pthread_create(&id1,NULL,(void*)mythread1,NULL);
if(ret)
{
printf("create pthread error!\n");
return -1;
}
ret=pthread_create(&id2,NULL,(void*)mythread2,NULL);
if(ret)
{
printf("create pthread error!\n");
return -1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
編譯步驟:gcc thread_create .c -lpthread -othread_create
例2: thread_int.c //向執行緒函式傳遞整形引數
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *create(void *arg)
{
int *num;
num=(int *)arg;
printf("create parameter is %d \n",*num);
return (void *)0;
}
int main(int argc,char *argv[])
{
pthread_t tidp;
int error;
int test=4;
int*attr=&test;
error=pthread_create(&tidp,NULL,create,(void*)attr);
if(error)
{
printf("pthread_create is created is not created...\n");
return -1;
}
sleep(1);
printf("pthread_create is created...\n");
return 0;
}
注:字串,結構引數,一樣道理
三、執行緒屬性
pthread_create()中的attr引數是一個結構指標,結構中的元素分別對應著新執行緒的執行屬性,主要包括以下幾項:
__detachstate,表示新執行緒是否與程序中其他執行緒脫離同步,如果置位則新執行緒不能用pthread_join()來同步,且在退出時自行釋放所佔用的資源。預設為PTHREAD_CREATE_JOINABLE狀態。這個屬性也可以線上程建立並執行以後用pthread_detach()來設定,而一旦設定為PTHREAD_CREATE_DETACH狀態(不論是建立時設定還是執行時設定)則不能再恢復到 PTHREAD_CREATE_JOINABLE狀態。
__schedpolicy,表示新執行緒的排程策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種,預設為SCHED_OTHER,後兩種排程策略僅對超級使用者有效。執行時可以用過 pthread_setschedparam()來改變。
__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示執行緒的執行優先順序。這個引數僅當排程策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在執行時通過pthread_setschedparam()函式來改變,預設為0。
__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新執行緒使用顯式指定排程策略和排程引數(即attr中的值),而後者表示繼承呼叫者執行緒的值。預設為PTHREAD_EXPLICIT_SCHED。
__scope,表示執行緒間競爭CPU的範圍,也就是說執行緒優先順序的有效範圍。POSIX的標準中定義了兩個值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有執行緒一起競爭CPU時間,後者表示僅與同進程中的執行緒競爭CPU。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。
pthread_attr_t結構中還有一些值,為了設定這些屬性,POSIX定義了一系列屬性設定函式,包括pthread_attr_init()、 pthread_attr_destroy()和與各個屬性相關的pthread_attr_get(),pthread_attr_set()函式。
pthread_create()中,第二個引數(pthread_attr_t)為將要建立的thread屬性。通常情況下配置為NULL,使用預設設定就可以了。但瞭解這些屬性,有利於更好的理解thread.
屬性物件(pthread_attr_t)是不透明的,而且不能通過賦值直接進行修改。系統提供了一組函式,用於初始化、配置和銷燬每種物件型別。
建立屬性:
int pthread_attr_init(pthread_attr_t *attr);
建立的屬性設定為預設設定。
銷燬屬性:
int pthread_attr_destroy(pthread_attr_t *attr);
一:設定分離狀態:
執行緒的分離狀態有2種:PTHREAD_CREATE_JOINABLE(非分離狀態), PTHREAD_CREATE_DETACHED(分離狀態)
分離狀態含義如下:
如果使用 PTHREAD_CREATE_JOINABLE 建立非分離執行緒,則假設應用程式將等待執行緒完成。也就是說,程式將對執行緒執行 pthread_join。 非分離執行緒在終止後,必須要有一個執行緒用 join 來等待它。否則,不會釋放該執行緒的資源以供新執行緒使用,而這通常會導致記憶體洩漏。因此,如果不希望執行緒被等待,請將該執行緒作為分離執行緒來建立。
如果使用 PTHREAD_CREATE_DETACHED 建立分離thread,則表明此thread在退出時會自動回收資源和thread ID.
Sam之前很喜歡使用分離thread. 但現在慢慢使用中覺得這樣是個不好的習慣。因為分離thread有個問題:主程式退出時,很難確認子thread已經退出。只好使用全域性變數來標明子thread已經正常退出了。
另外:不管建立分離還是非分離的thread.在子thread全部退出之前退出主程式都是很有風險的。如果主thread選擇return,或者呼叫exit()退出,則所有thread都會被kill掉。這樣很容易出錯。Sam上次出的問題其實就是這個。但如果主thread只是呼叫pthread_exit().則僅主執行緒本身終止。程序及程序內的其他執行緒將繼續存在。所有執行緒都已終止時,程序也將終止。
intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
得到當前和分離狀態和設定當前的分離狀態。
二:設定棧溢位保護區大小:
棧溢位概念:
· 溢位保護可能會導致系統資源浪費。如果應用程式建立大量執行緒,並且已知這些執行緒永遠不會溢位其棧,則可以關閉溢位保護區。通過關閉溢位保護區,可以節省系統資源。
· 執行緒在棧上分配大型資料結構時,可能需要較大的溢位保護區來檢測棧溢位。
int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
設定和得到棧溢位保護區。如果guardsize設為0。則表示不設定棧溢位保護區。guardsize 的值向上舍入為PAGESIZE 的倍數。
三:設定thread競用範圍:
競用範圍(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)指 使用 PTHREAD_SCOPE_SYSTEM 時,此執行緒將與系統中的所有執行緒進行競爭。使用 PTHREAD_SCOPE_PROCESS 時,此執行緒將與程序中的其他執行緒進行競爭。
int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
四:設定執行緒並行級別:
int pthread_getconcurrency(void);
int pthread_setconcurrency(int new_level);
Sam不理解這個意思。
五:設定排程策略:
POSIX 標準指定 SCHED_FIFO(先入先出)、SCHED_RR(迴圈)或 SCHED_OTHER(實現定義的方法)的排程策略屬性。
· SCHED_FIFO
如果呼叫程序具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM) 的先入先出執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,則會繼續處理該執行緒,直到該執行緒放棄或阻塞為止。對於具有程序爭用範圍 (PTHREAD_SCOPE_PROCESS)) 的執行緒或其呼叫程序沒有有效使用者 ID 0 的執行緒,請使用 SCHED_FIFO。SCHED_FIFO 基於 TS 排程類。
· SCHED_RR
如果呼叫程序具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM)) 的迴圈執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,並且這些執行緒沒有放棄或阻塞,則在系統確定的時間段內將一直執行這些執行緒。對於具有程序爭用範圍 (PTHREAD_SCOPE_PROCESS) 的執行緒,請使用SCHED_RR(基於 TS 排程類)。此外,這些執行緒的呼叫程序沒有有效的使用者ID 0。
SCHED_FIFO 是基於佇列的排程程式,對於每個優先順序都會使用不同的佇列。SCHED_RR 與 FIFO 相似,不同的是前者的每個執行緒都有一個執行時間配額。
int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
六:設定優先順序:
int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);
int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
conststruct sched_param *restrict param);
比較複雜,Sam沒去研究。
七:設定棧大小:
當建立一個thread時,會給它分配一個棧空間,執行緒棧是從頁邊界開始的。任何指定的大小都被向上舍入到下一個頁邊界。不具備訪問許可權的頁將被附加到棧的溢位端(第二項設定中設定)。
指定棧時,還應使用 PTHREAD_CREATE_JOINABLE 建立執行緒。在該執行緒的 pthread_join() 呼叫返回之前,不會釋放該棧。在該執行緒終止之前,不會釋放該執行緒的棧。瞭解這類執行緒是否已終止的唯一可靠方式是使用pthread_join。
一般情況下,不需要為執行緒分配棧空間。系統會為每個執行緒的棧分配指定大小的虛擬記憶體。
#ulimit -a可以看到這個預設大小
四、執行緒終止
如果程序中的任一執行緒呼叫了exit,_Exit或者_exit,那麼整個程序就會終止。與此類似,如果訊號的預設動作是終止程序,那麼,把該訊號傳送到執行緒會終止整個程序。
單個執行緒可以通過下列三種方式退出,在不終止整個程序的情況下停止它的控制流。
(1):從啟動例程中返回,返回值是執行緒的退出碼
(2):執行緒可以被同一程序中的其他執行緒取消
(3):執行緒呼叫pthread_exit()
pthread_exit函式:
- 原型: void pthread_exit(void *rval_ptr);
- 標頭檔案: <pthread.h>
- 引數: rval_ptr是一個無型別指標, 指向執行緒的返回值儲存變數.
pthread_join函式:
- 原型: int pthread_join(pthread_t thread, void **rval_ptr);
- 標頭檔案: <pthread.h>
- 返回值: 成功則返回0, 否則返回錯誤編號.
- 引數:
- thread: 執行緒ID.
- rval_ptr: 指向返回值的指標(返回值也是個指標).
- 說明:
- 呼叫執行緒將一直阻塞, 直到指定的執行緒呼叫pthread_exit, 從啟動例程返回或被取消.
- 如果執行緒從它的啟動例程返回, rval_ptr包含返回碼.
- 如果執行緒被取消, 由rval_ptr指定的記憶體單元置為: PTHREAD_CANCELED.
- 如果對返回值不關心, 可把rval_ptr設為NULL.
例項:
#include <pthread.h>
#include <stdio.h>
/* print process and thread IDs */
void printids(const char *s)
{
pid_t pid, ppid;
pthread_t tid;
pid= getpid();
ppid = getppid();
tid = pthread_self();
printf("%16s pid %5u ppid %5u tid %16u (0x%x) ",
s, (unsigned int)pid, (unsigned int)ppid,
(unsigned int)tid, (unsigned int)tid);
}
/* thread process */
void *thread_func(void *arg);
{
printids("new thread: ");
return (void *)108;
}
/* main func */
int main()
{
int err;
void *tret; /* thread return value */
pthread_t ntid;
err = pthread_create(&ntid, NULL, thread_func, NULL);
if (err != 0)
perror("can't create thread");
err = pthread_join(ntid, &tret);
if (err != 0)
perror("can't join thread");
printids("main thread: ");
printf("thread exit code: %d ", (int)tret);
sleep(1);
return 0;
}
這段程式碼是通過前一個例項改編的, 執行流程如下:
- 首先建立一個新執行緒, 該執行緒在列印完IDs後, 返回108.
- 然後用pthread_join獲取該執行緒返回值, 存於tret中.
- 主執行緒列印IDs.
- 主執行緒列印tret, 即新執行緒的返回值.
執行緒取消的定義:
一般情況下,執行緒在其主體函式退出的時候會自動終止,但同時也可以因為接收到另一個執行緒發來的終止(取消)請求而強制終止。
執行緒取消的語義:
執行緒取消的方法是向目標執行緒發Cancel訊號,但如何處理Cancel訊號則由目標執行緒自己決定,或者忽略、或者立即終止、或者繼續執行至Cancelation-point(取消點),由不同的Cancelation狀態決定。
執行緒接收到CANCEL訊號的預設處理(即pthread_create()建立執行緒的預設狀態)是繼續執行至取消點,也就是說設定一個CANCELED狀態,執行緒繼續執行,只有執行至Cancelation-point的時候才會退出。
取消點定義:
根據POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函式以及read()、write()等會引起阻塞的系統呼叫都是Cancelation-point,而其他pthread函式都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函式都不是Cancelation-point;但CANCEL訊號會使執行緒(http://blog.csdn.net/shanzhizi)從阻塞的系統呼叫中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統呼叫前後呼叫 pthread_testcancel(),從而達到POSIX標準所要求的目標,即如下程式碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
程式設計方面的考慮:
如果執行緒處於無限迴圈中,且迴圈體內沒有執行至取消點的必然路徑,則執行緒無法由外部其他執行緒的取消請求而終止。因此在這樣的迴圈體的必經路徑上應該加入pthread_testcancel()呼叫。即如下程式碼段:
While(1)
{
………
pthread_testcancel();
}
與執行緒取消相關的pthread函式:
intpthread_cancel(pthread_t thread):執行緒可以通過呼叫pthread_cancel函式來請求取消同一程序中的其他程序。
傳送終止訊號給thread執行緒,如果成功則返回0,否則為非0值。傳送成功並不意味著thread會終止。注意pthread_cancel並不等待執行緒終止,它僅僅提出請求。
int pthread_setcancelstate(int state, int*oldstate):
設定本執行緒對Cancel訊號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(預設)和 PTHREAD_CANCEL_DISABLE,分別表示收到訊號後設為CANCLED狀態和忽略CANCEL訊號繼續執行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。
int pthread_setcanceltype(int type, int*oldtype)
設定本執行緒取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到訊號後繼續執行至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為NULL則存入原來的取消動作型別值。
void pthread_testcancel(void)
檢查本執行緒是否處於Canceld狀態,如果是,則進行取消動作,否則直接返回。
http://blog.csdn.net/shanzhizi
執行緒可以安排它退出時需要呼叫的函式,這與程序可以用atexit函式安排程序退出時需要呼叫的函式是類似的。執行緒可以建立多個清理處理程式。處理程式記錄在棧中,也就是說它們的執行順序與它們註冊時的順序相反。
#include <pthread.h>
void pthread_cleanup_push(void(*rtn)(void*),void *arg);
void pthread_cleanup_pop(int execute);
當執行緒執行以下動作時呼叫清理函式,呼叫引數為arg,清理函式的呼叫順序用pthread_cleanup_push來安排。
呼叫pthread_exit時
響應取消請求時
用非0的execute引數呼叫pthread_cleanup_pop時。
如果執行緒是通過從它的啟動例程中返回而終止的話,那麼它的清理處理程式就不會被呼叫,還要注意清理處理程式是按照與它們安裝時相反的順序被呼叫的。
int pthread_detach(pthread_t tid);
可以用於使執行緒進入分離狀態。