1. 多線程的優勢:
[1]. 通過合理的分配任務到多個線程,每個線程在進行事件處理時可以採用同步編程模式,相比異步編程,同步編程簡單方便很多
[2]. 多個進程間進行數據交互必須通過各種IPC機制,而同一個進程下的多個線程間共享同一片地址空間,數據可以直接交互
[3]. 通過合理的分配任務到多個線程,可以改善整個程序的響應時間和併發性能,即便是運行在單核CPU上,只要不是CPU密集型的程序,都可以通過多線程改善性能
2. POSIX線程模型
linux正式支持POSIX線程模型大約是從kernel-2.6和glibc-2.3開始,具體的名叫 Native POSIX Threads Library(NPTL),有關NPTL的深入分析將會另寫一份筆記
編寫基於POSIX線程的程序時,需要在代碼中加入"#include <pthread.h>",然後在編譯代碼時加入"-lpthread"即可
編寫基於POSIX線程的程序時,對於出錯的處理通常不要依賴errno,而是應該基於pthread系列函數返回的錯誤碼進行出錯處理
3. 線程ID(pthread_t)
類似於進程ID的定義,線程ID用於在所在進程中唯一標識一個線程,雖然在linux中使用無符號長整型表示pthread_t,但不建議在實際操作中直接當作整數處理
/* API : int pthread_equal(pthread_t tid1,pthread_t tid2)
* 描述 : 用來比較兩個線程ID,相等返回非0,不相等返回0
*/
/* API : pthread_t pthread_self(void)
* 描述 : 獲取自身的線程ID
*/
4. 線程屬性(pthread_attr_t)
線程屬性對象pthread_attr_t中包含了多個屬性,但pthread_attr_t的內部結構細節被隱藏在了NPTL中,應用程序通過NPTL提供的一組API來管理線程屬性
線程屬性包括以下這些:
線程的分離狀態
線程的棧屬性
線程的調度策略
線程的調度參數
線程的繼承性
線程的作用域
線程的棧末尾警戒緩衝區大小
線程屬性對象在使用前必須經過初始化,相對應的,使用完畢後也必須執行銷燬
/* API : int pthread_attr_init(pthread_attr_t *attr)
* 描述 : 初始化一個線程屬性對象attr
* @attr - 指向要被初始化的線程屬性對象
*
* 備註 : 初始化之後,attr中存放的就是線程屬性的默認值;
* 不允許對已經初始化的線程屬性對象重複進行初始化
*/
/* API : int pthread_attr_destroy(pthread_attr_t *attr)
* 描述 : 銷燬一個線程屬性對象attr
* @attr - 指向要被銷燬的線程屬性對象
*
* 備註 : 銷燬線程屬性對象並不會對已經創建的線程(創建時使用了該對象)有任何影響;
* 線程屬性對象在被銷燬之後,又可以被重新初始化
*/
線程的分離狀態屬性決定了線程終止時的行爲:
非分離的線程在結束時不會自行釋放佔用的系統資源,而是要等到有線程對該線程調用了pthread_join時纔會完全釋放;
已經分離的線程在結束時會立即釋放自身佔用的系統資源,並且其他線程也不允許再調用pthread_join來等待它的終止狀態
/* API : int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate)
* 描述 : 獲取線程屬性對象中的分離狀態屬性
* @attr - 指向線程屬性對象
* @detachstate - 用於存放線程的分離狀態屬性:
* PTHREAD_CREATE_DETACHED - 以分離狀態啓動線程
* PTHREAD_CREATE_JOINABLE - 以非分離狀態啓動線程(默認屬性)
*/
/* API : int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate)
* 描述 : 修改線程屬性對象中的分離狀態屬性
* @attr - 指向線程屬性對象
* @detachstate - 用於設置線程的分離狀態屬性
*
* 備註 : 如果創建線程前就確定不需要關心該線程的終止狀態,就調用本函數提前將線程的分離狀態屬性設置爲PTHREAD_CREATE_DETACHED
*/
/* API : int pthread_detach(pthread_t tid)
* 描述 : 分離指定線程
* @tid - 要被分離的線程ID
*
* 備註 : 如果對已經存在的某個線程的終止狀態不再關心,就調用本函數將其分離
*/
線程的棧屬性又包括了棧地址、棧大小以及棧末尾的警戒緩衝區大小
/* API : int pthread_attr_getstack(const pthread_attr_t *attr,void **stackaddr,size_t *stacksize)
* 描述 : 獲取線程屬性對象中的自定義的棧地址和棧大小屬性
* @attr - 指向線程屬性對象
* @stackaddr - 用於存放自定義的線程的棧地址
* @stacksize - 用於存放自定義的線程的棧大小
*
* 備註 : (glibc2.19 + kernel3.16.36環境) 本函數只能獲取通過pthread_attr_setstack設置的自定義的線程棧地址和棧大小屬性,
* 如果沒有設置過自定義值,本函數返回空值。
* 本函數的這個特性APUE並未闡明,網上的資料大都是相互抄襲,人云亦云。
* 這裏,我只能說,本函數的這個特性至少是跟glibc和kernel的版本相關的
*/
/* API : int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize)
* 描述 : 自定義線程屬性對象中的棧地址和棧大小屬性
* @attr - 指向線程屬性對象
* @stackaddr - 用於設置自定義的線程棧地址(通常要保證page邊界對齊),該地址將會作爲線程棧的最低可尋址地址
* @stacksize - 用於設置自定義的線程棧大小(通常要保證page大小的整數倍)
*
* 備註 : 自定義線程棧的步驟:
* [1]. 調用malloc/mmap系列函數從堆中分配空間
* [2]. 調用本函數改變新建線程的棧位置
* [3]. 創建線程
*/
/* API : int pthread_attr_getstacksize(pthread_attr_t *attr,size_t *stacksize)
* 描述 : 獲取線程屬性對象中的棧大小屬性
* @attr - 指向線程屬性對象
* @stacksize - 用於存放線程棧大小
*
* 備註 : 不同於pthread_attr_getstack,如果沒有設置過自定義值,本函數返回缺省的棧大小(通常就是8M);如果設置了自定義值,本函數返回自定義值
*/
/* API : int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize)
* 描述 : 設置線程屬性對象中的棧大小屬性
* @attr - 指向線程屬性對象
* @stacksize - 用於設置自定義的線程棧大小(不能小於PTHREAD_STACK_MIN,並且通常要保證STACK_ALIGN的整數倍)
*
* 備註 : 相比pthread_attr_setstack,本函數適用場景:即希望改變默認的棧大小,又不想自己處理線程棧的分配問題
*/
/* API : int pthread_attr_getguardsize(pthread_attr_t *attr,size_t *guardsize)
* 描述 : 獲取線程屬性對象中的棧末尾警戒緩衝區大小屬性
* @attr - 指向線程屬性對象
* @guardsize - 用於存放棧末尾警戒緩衝區大小(其缺省值一般是page大小)
*
* 備註 : 棧末尾警戒緩衝區用於避免棧溢出,具體的原理詳見另一篇棧相關的文檔
* APUE中寫道,"if we change the stackaddr thread attribute, the system assumes that we will be
* managing our own stacks and disables stack guard buffers, just as if we had set the
* guardsize thread attribute to 0"
* 在本文的測試環境Debian 8.3(glibc2.19 + kernel3.16.36)中,結果並非這樣:在調用了pthread_attr_setstack之後,guardsize的值仍舊是缺省的page大小
*/
/* API : int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize)
* 描述 : 設置線程屬性對象中的棧末尾警戒緩衝區大小屬性
* @attr - 指向線程屬性對象
* @guardsize - 用於設置警戒緩衝區大小
*
* 備註 : 雖然不被建議這麼做,但是可以將guardsize的值設爲0,意味着不再提供警戒緩衝區機制
*/
附錄:測試代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_t ntid;
pthread_attr_t attr;
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid = %lu,tid = %lu(0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,(unsigned long)tid);
}
void *thr_fn(void *arg)
{
printids("new thread:");
return (void *)2;
}
int main()
{
int err;
if(pthread_attr_init(&attr)){
printf("init pthread attr error\n");
return -1;
}
int detachstate;
if(pthread_attr_getdetachstate(&attr,&detachstate)){
printf("get pthread's detach state error\n");
return -1;
}
printf("pthread's detach state = %s\n",detachstate == PTHREAD_CREATE_JOINABLE?"joinable":"detach");
size_t len = 1024 * 1000 * 4;
void *buf = malloc(len);
printf("malloc pthread stack addr = %p, len = %d\n",buf,len);
if(pthread_attr_setstack(&attr,buf,len)){
printf("set ptherad's stack attr error\n");
return -1;
}
printf("set pthread stack attr ok\n");
void *stackaddr;
size_t stacksize;
if(pthread_attr_getstack(&attr,&stackaddr,&stacksize)){
printf("get ptherad's stack attr error\n");
return -1;
}
printf("pthread's stack addr = %p,size = %d\n",stackaddr,stacksize);
if(pthread_attr_getstacksize(&attr,&stacksize)){
printf("get ptherad's stack size error\n");
return -1;
}
printf("pthread's stack size = %d\n",stacksize);
size_t guardsize;
if(pthread_attr_getguardsize(&attr,&guardsize)){
printf("get ptherad's stack size error\n");
return -1;
}
printf("pthread's guard size = %d\n",guardsize);
err = pthread_create(&ntid,NULL,thr_fn,NULL);
if(err){
printf("create ptherad error = %d\n",err);
return -1;
}
printids("main thread:");
sleep(1);
if(pthread_attr_destroy(&attr)){
printf("destory ptherad attr error \n");
return -1;
}
void *state;
if(pthread_join(ntid,&state)){
printf("join ptherad error \n");
return -1;
}
printf("join pthread ok,state = %d\n",(unsigned int)state);
exit(0);
}