對比
進程 線程
fork pthread_create
exit (10) pthread_exit (void *)
wait (int *) pthread_join (,void **)阻塞
kill pthread_cancel ();必須到取消點(檢查點):系統調用 man 7 pthreads
getpid pthread_self 命名空間pthread_detach:分離,好處:自動清理pcb
什么類型作為返回值,回收就用哪個類型的地址
線程屬性
linux 下線程的屬性是可以根據實際項目需要,進行設置,之前我們討論的線程都是采用 線程的默認屬性,默認屬性已經可以解決絕大多數開發時遇到的問題。
如我們對程序的性能提出更高的要求那么需 要設置線程屬性,比如可以通過設置線程棧的大小來降低內存的使用,增加最大線程個數。
主要結構體成員:
- 線程分離狀態
- 線程棧大小(默認8M平均分配)
- 線程棧警戒緩沖區大小(位于棧末尾) 為了防止線程溢出
線程屬性說明
- 屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為
pthread_attr_init
,這個函數必須在pthread_create
函數之前調用。之后須用pthread_attr_destroy
函數來釋放資源。 - 線程屬性主要包括如下屬性:**作用域(scope)、棧尺寸(stacksize)、棧地址(stackaddress)、優先級(priority)、 分離的狀態(detachedstate)、調度策略和參數(schedulingpolicyandparameters)。**默認的屬性為非綁定、非分離、 缺省的堆棧、與父進程同樣級別的優先級。
線程屬性控制流程
線程屬性初始化
注意:應先初始化線程屬性,再 pthread_create 創建線程
- 初始化線程屬性
int pthread_attr_init(pthread_attr_t *attr);
成功:0;失敗:錯誤號 - 銷毀線程屬性所占用的資源
int pthread_attr_destroy(pthread_attr_t *attr);
成功:0;失敗:錯誤號
線程的分離狀態
線程的分離狀態決定一個線程以什么樣的方式來終止自己。
- 非分離狀態:線程的默認屬性是非分離狀態,這種情況下,原有的線程等待創建的線程結束。只有當 pthread_join() 函數返回時,創建的線程才算終止,才能釋放自己占用的系統資源。
- 分離狀態:分離線程沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。應該 根據自己的需要,選擇適當的分離狀態。
線程分離狀態的函數:
設置線程屬性,分離 or 非分離 int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
獲取線程屬性,分離 or 非分離 int pthread_attr_getdetachstate (pthread_attr_t *attr,int *detachstate);
參數: attr:已初始化的線程屬性detachstate: PTHREAD_CREATE_DETACHED(分離線程) PTHREAD_CREATE_JOINABLE(非分離線程)
示例1:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>void *thrd_func(void *arg)
{pthread_exit((void *)77);
}int main(void)
{pthread_t tid;int ret;pthread_attr_t attr;ret = pthread_attr_init(&attr);if(ret != 0){ fprintf(stderr,"pthread_init error: %s\n",strerror(ret));exit(1);} pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);ret = pthread_create(&tid,&attr,thrd_func,NULL); if(ret != 0){ fprintf(stderr,"pthread_create error: %s\n",strerror(ret));exit(1);} ret = pthread_join(tid,NULL);printf("---------join ret = %d\n",ret);pthread_exit((void *)1);
}
示例2
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>void *tfn(void *arg)
{int n=3;while(n--){printf("thread count %d\n",n);sleep(1);}return (void *)1;
}int main(void)
{pthread_t tid;void *tret;int err;#if 1pthread_attr_t attr; //通過線程屬性類設置游離態pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid,&attr,tfn,NULL);#elsepthread_create(&tid,NULL,tfn,NULL);pthread_detach(tid); //讓線程分離 ----自動退出,無系統殘留資源
#endifwhile(1){err = pthread_join(tid,&tret);printf("---------err= %d\n",err);if(err != 0)fprintf(stderr,"thread error: %s\n",strerror(err));elsefprintf(stderr,"thread exit code %d\n",(int)tret);sleep(1);}return 0;
}
注意事項
- 如果設置一個線程為分離線程,而這個線程運行又非常快,它很可能在 pthread_create 函數返回之前就終止了,它終止以后就可能將線程號和系統資源移交給其他的線程使用,這樣調用 pthread_create 的線程就得到了錯誤的線程號。
- 要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創建的線 程里調用 pthread_cond_timedwait 函數,讓這個線程等待一會兒,留出足夠的時間讓函數 pthread_create 返回。
- 設 置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如 wait()之類的函數,它們是使整個進程睡
眠,并不能解決線程同步的問題。
線程的棧大小
當系統中有很多線程時,可能需要減小每個線程棧的默認大小,防止進程的地址空間不夠用,當線程調用的函 數會分配很大的局部變量或者函數調用層次很深時,可能需要增大線程棧的默認大小。
函數 pthread_attr_getstacksize
和 pthread_attr_setstacksize
提供設置。
int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);
成功:0;失敗:錯誤號int pthread_attr_getstacksize(pthread_attr_t *attr,size_t *stacksize);
成功:0;失敗:錯誤號- 參數:
attr:指向一個線程屬性的指針 stacksize:返回線程的堆棧大小
默認線程棧均分8M空間
查看一個進程最多能創建多少個線程
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>void *tfn(void *arg)
{while(1)sleep(1);
}int main(void)
{pthread_t tid;int ret,count = 1;for(;;){ret = pthread_create(&tid,NULL,tfn,NULL);if(ret != 0){ printf("%s\n",strerror(ret)); break;} printf("-----------%d\n",++count);} return 0;
}
我的是能創建4094個線程
線程屬性控制示例
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>#define SIZE 0x100000void *th_fun(void * arg)
{while(1);sleep(1);
}int main(void)
{pthread_t tid;int err, detachstate,i=1;pthread_attr_t attr;size_t stacksize;void *stackaddr;pthread_attr_init(&attr);pthread_attr_getstack(&attr,&stackaddr,&stacksize);pthread_attr_getdetachstate(&attr,&detachstate);if(detachstate == PTHREAD_CREATE_DETACHED)//默認是分離態printf("thread detached\n");else if(detachstate == PTHREAD_CREATE_JOINABLE)//默認非分離printf("thread join\n");elseprintf("thread un known\n");//設置線程分離pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);while(1){//在堆上申請內存,指定線程棧的起始地址和大小stackaddr = malloc(SIZE);if(stackaddr == NULL){perror("malloc");exit(1);}stacksize = SIZE;pthread_attr_setstack(&attr,stackaddr,stacksize);//借助線程的屬性,修
改線程棧空間大小err = pthread_create(&tid,&attr,th_fun,NULL);if(err != 0){printf("%s\n",strerror(err));exit(1);}printf("%d\n",i++);//i表示循環創建多少次}pthread_attr_destroy(&attr);return 0;
}
NPTL(線程庫)
- 查看當前 pthread 庫版本 getconfGNU_LIBPTHREAD_VERSION
- NPTL 實現機制(POSIX),NativePOSIXThreadLibrary
- 使用線程庫時 gcc 指定 –lpthread
線程使用注意事項
- 主線程退出其他線程不退出,主線程應調用 pthread_exit
- 避免僵尸線程
pthread_join
pthread_detach
pthread_create 指定分離屬性
被 join 線程可能在 join 函數返回前就釋放完自己的所有內存資源,所以不應當返回被回收線程棧中的值; - malloc 和 mmap 申請的內存可以被其他線程釋放
- 應避免在多線程模型中調用 fork 除非,馬上 exec,子進程中只有調用 fork 的線程存在,其他線程在子進程 中均 pthread_exit
- 信號的復雜語義很難和多線程共存,應避免在多線程引入信號機制