/*linux
演示linux線程的基本操做程序員
*/編程
#include <stdio.h>數據結構
#include <stdlib.h>多線程
#include <unistd.h>函數
#include <pthread.h>測試
#define __DEBUGui
#ifdef __DEBUGspa
#define DBG(fmt,args...) fprintf(stdout, fmt, ##args)線程
#else
#define DBG(fmt,args...)
#endif
#define ERR(fmt,args...) fprintf(stderr, fmt, ##args)
static int isThreadQuit = 0;
void SetXxThreadQuit()
{
/*quit*/
isThreadQuit = 1;
}
void *XxManageThread(void *arg)
{
char *cmd = (char*)arg;
DBG("arg value=%s\n",cmd);
while(isThreadQuit==0){
DBG("[1] thread running\n");
sleep(1);
}
/*arg是將指針帶進來,cmd則相反,或者設置 NULL*/
pthread_exit(cmd);
//pthread_exit(NULL);
}
int XxManageThreadInit()
{
pthread_t tManageThread;
char *any="any value";
char *retn;
int ret;
/*
第二個參數是設置線程屬性,通常不多用到(設置優先級等),第四個參數爲傳遞到線程的指針,
能夠爲任何類型
*/
ret = pthread_create(&tManageThread,NULL,XxManageThread,any);
if(ret == -1){
/*成功返回0.失敗返回-1*/
ERR("Ctreate Thread ERROR\n");
return -1;
}
/*
設置線程退出時資源的清理方式,若是是detach,退出時會自動清理
若是是join,則要等待pthread_join調用時纔會清理
*/
pthread_detach(tManageThread);
//pthread_join(tManageThread,retn);
//DBG("retn value=%s\n",retn);
return 0;
}
#define TEST_MAIN
#ifdef TEST_MAIN
int main()
{
printf("hello liuyu\n");
int count=3;
if(XxManageThreadInit()==-1){
exit(1);
}
while(count--){
DBG("[0] main running\n");
sleep(2);
}
SetXxThreadQuit();
DBG("waitting thread exit...\n");
while(1);
return 0;
}
#endif
一 線程建立
pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:
typedef unsigned long int pthread_t;
它是一個線程的標識符。函數pthread_create用來建立一個線程,它的原型爲:
extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
void *(*__start_routine) (void *), void *__arg));
參數解釋以下:
thread : 用於返回建立的線程的ID;每一個線程都有本身的ID,在線程內能夠調用pthread_self()函數獲取ID值,該函數原型是這樣的:pthread_t pthread_self()。
attr : 用於指定將要被建立的線程的屬性;該值能夠爲NULL,表示默認的屬性。等下專門說下這個屬性,暫時知道這個參數表示用於建立線程的方式就能夠了;
start_routine : 這是一個函數的指針,指定線程被調度時的入口;
arg : 用於線程入口函數的參數;
這裏,咱們的函數thread不須要參數,因此最後一個參數設爲空指針。第二個參數咱們也設爲空指針,這樣將生成默認屬性的線程。對線程屬性的設定和修改在下一節闡述。當建立線程成功時,函數返回0,若不爲0則說明建立線程失敗,常見的錯誤返回代碼爲EAGAIN和EINVAL。前者表示系統限制建立新的線程,例如線程數目過多了;後者表示第二個參數表明的線程屬性值非法。建立線程成功後,新建立的線程則運行參數三和參數四肯定的函數,原來的線程則繼續運行下一行代碼。
函數pthread_join用來等待一個線程的結束。函數原型爲:
extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。一個線程的結束有兩種途徑,一種是象咱們上面的例子同樣,函數結束了,調用它的線程也就結束了;另外一種方式是經過函數pthread_exit來實現。它的函數原型爲:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
惟一的參數是函數的返回代碼,只要pthread_join中的第二個參數thread_return不是NULL,這個值將被傳遞給thread_return。最後要說明的是,一個線程不能被多個線程等待,不然第一個接收到信號的線程成功返回,其他調用pthread_join的線程則返回錯誤代碼ESRCH。
在這一節裏,咱們編寫了一個最簡單的線程,並掌握了最經常使用的三個函數pthread_create,pthread_join和pthread_exit。下面,咱們來了解線程的一些經常使用屬性以及如何設置這些屬性。
二 修改線程的屬性
在上一節的例子裏,咱們用pthread_create函數建立了一個線程,在這個線程中,咱們使用了默認參數,即將該函數的第二個參數設爲NULL。的確,對大多數程序來講,使用默認屬性就夠了,但咱們仍是有必要來了解一下線程的有關屬性。
屬性結構爲pthread_attr_t,它一樣在頭文件/usr/include/pthread.h中定義.線程屬性由數據結構pthread_attr_t結構表示,其定義以下所示:
typedef struct
{
int detachstate; 線程的分離狀態
int schedpolicy; 線程調度策略
struct sched_param schedparam; 線程的調度參數
int inheritsched; 線程的繼承性
int scope; 線程的做用域
size_t guardsize; 線程棧末尾的警惕緩衝區大小
int stackaddr_set;
void * stackaddr; 線程棧的位置
size_t stacksize; 線程棧的大小
}pthread_attr_t;
detachstate: 線程的分離狀態表示線程會以什麼樣的方式來終止本身,或者說該狀態用來表示建立的線程是否保持與進程中其餘的線程一種同步。若是這個沒有被置位,那就不能用pthread_join調用來同步,當線程終止時,自行釋放所佔用的資源,而不用在pthread_join返回以後,再來釋放。該值默認的狀況是PTHREAD_CREATE_JOINABLE狀態,在線程建立以後,能夠調用pthread_detach()來設置爲PTHREAD_CREATE_DETACH狀態。一旦設置了這個狀態建立線程以後,將不能被從新設爲PTHREAD_CREATE_JOINABLE 狀態;
schepolicy : 表示線程被調度的策略。主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種,缺省爲SCHED_OTHER,後兩種調度策略僅對超級用戶有效。運行時能夠用過 pthread_setschedparam()來改變,該函數原型是這樣的:int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param)。這個調用能夠動態的改變調度策略和線程的優先級。
scheparam: 一個struct sched_param結構,結構體包含一個sched_priority整型變量表示線程的運行優先級。這個參數僅當調度策略爲實時(即SCHED_RR 或SCHED_FIFO)時纔有效,並能夠在運行時經過pthread_setschedparam()函數來改變,缺省爲0。
inheritshed : 表示線程建立的繼承;調用函數pthread_attr_setinheritsched和pthread_attr_getinheritsched用來設置和獲得線程的繼承屬性。有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調度策略和調度參數(即attr中的值),然後者表示繼承調用者線程的值。缺省爲PTHREAD_EXPLICIT_SCHED。
scope : 表示線程間競爭CPU的範圍,也就是說線程優先級的有效範圍。POSIX的標準中定義了兩個值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中全部線程一塊兒競爭CPU時間,後者表示僅與同進程中的線程競爭CPU。能夠經過調用pthread_attr_getscope(), pthread_attr_setscope(),用於獲取屬性中的線程的做用域。
stacksize,stackaddr : 表示線程堆棧的大小和地址。也有四個函數用於設置和獲取相應的值。pthread_attr_getstatcksize(),pthread_attr_setstatcksize(),pthread_attr_setstackaddr(),pthread_attr_getstatckaddr()。
guardsize : 控制着線程棧末尾以後以免棧溢出的擴展內存大小。一樣能夠調用pthread_attr_setguardsize()和pthread_attr_getguardsize()。
這個結構體在使用過程當中由pthread_attr_init和pthread_attr_destory負責數據的初始化和銷燬;這個函數必須在pthread_create函數以前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先級。默認的屬性爲非綁定、非分離、缺省1M的堆棧、與父進程一樣級別的優先級。
關於線程的綁定,牽涉到另一個概念:輕進程(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進行存放,通常說來,咱們老是先取優先級,對取得的值修改後再存放回去。下面便是一段簡單的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, ¶m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
三 終止線程
分爲如下幾種:
一、正常終止
線程在執行完成以後,正常終止。
二、線程取消
通常狀況下,線程在其主體函數退出的時候會自動終止,但同時也能夠由於接收到另外一個線程發來的終止(取消)請求而而強制終止。
線程取消的方法是向目標線程發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信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,所以能夠在須要做爲Cancelation-point的系統調用先後調用 pthread_testcancel(),從而達到POSIX標準所要求的目標,即以下代碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
程序設計方面的考慮
若是線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程沒法由外部其餘線程的取消請求而終止。所以在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。
與線程取消相關的pthread函數
int pthread_cancel(pthread_t thread)
發送終止信號給thread線程,若是成功則返回0,不然爲非0值。發送成功並不意味着thread會終止。
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狀態,若是是,則進行取消動做,不然直接返回。
3,pthread_exit
在線程內顯示的調用,以終止線程。
4,等待終止
在一個線程裏面,咱們要等待另外一個線程結束。能夠經過調用pthread_join函數。這將會在調用線程引發阻塞,而且在建立thread所指定的線程時,其分離屬性必須是pthread_create_joinable.不然調用出錯。
五、殺死線程
該函數能夠用於向指定的線程發送信號:
int pthread_kill(pthread_t threadId,int signal);
若是線程內不對信號進行處理,則調用默認的處理程式,如SIGQUIT會退出終止線程,SIGKILL會殺死線程等等,能夠調用signal(SIGQUIT, sig_process_routine); 來自定義信號的處理程序。
傳遞的pthread_kill的signal參數通常都是大於0的,這時系統默認或者自定義的都是有相應的處理程序。signal爲0時,是一個被保留的信號,通常用這個保留的信號測試線程是否存在。
pthread_kill 返回值以下:
0:調用成功。
ESRCH:線程不存在。
EINVAL:信號不合法。
int kill_ret = pthread_kill(thread_id,0);
if(kill_ret == ESRCH)
printf("指定的線程不存在或者是已經終止\n");
else if(kill_ret == EINVAL)
printf("調用傳遞一個無用的信號\n");
else
printf("線程存在\n");