linux多線程設計是指基於Linux操作系統下的多線程設計,包括多任務程序的設計,併發程序設計,網絡程序設計,數據共享等。Linux系統下的多線程遵循POSIX線程接口,稱爲pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫libpthread.a。
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不需要參數,所以最後一個參數設爲空指針。第二個參數我們也設爲空指針,這樣將生成默認屬性的線程。對線程屬性的設定和修改我們將在下一節闡述。當創建線程成功時,函數返回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。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define THREAD_NUMBER 3 //線程數
#define REPEAT_NUMBER 5 //每個線程中的小任務數
#define DELAY_TIME_LEVELS 10.0 //小任務之間的最大時間間隔
void *thrd_func(void *arg) //線程函數例程
{
int thrd_num = (int)arg;
int delay_time = 0;
int count = 0; //執行時線程共享
printf("Thread %d is starting/n",thrd_num);
for(count = 0;count < REPEAT_NUMBER;count++)
{
delay_time = (int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX)) + 1; //時間隨即
sleep(delay_time);
printf("/tThread %d:job %d delay = %d/n",thrd_num,count,delay_time);
}
printf("Thread %d finished/n",thrd_num);
pthread_exit(NULL); //線程退出
}
int main()
{
pthread_t thread[THREAD_NUMBER]; //線程數
int no = 0,res;
void *thrd_ret;
srand(time(NULL));
for(no = 0; no < THREAD_NUMBER;no++) //創建多線程
{
res = pthread_create(&thread[no],NULL,thrd_func,(void*)no); //調用函數thrd_func()
if(res!=0)
{
printf("Create thread %d failed/n",no);
exit(res);
}
}
printf("Create threads success/n Waiting for threads to finish.../n");
for(no = 0;no<THREAD_NUMBER;no++)
{
res = pthread_join(thread[no],&thrd_ret); //用於將當前線程掛起來等待線程的結束
if(!res)
{
printf("Thread %d joined/n",no);
}
else
{
printf("Thread %d join failed/n",no);
}
}
return 0;
}
互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最後得到的結果一定是災難性的。
斥鎖變量mutex,結構pthread_mutex_t爲不公開的數據類型,其中包含一個系統分配的屬性對象。函數pthread_mutex_init用來生成一個互斥鎖。NULL參數表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調用函數pthread_mutexattr_init。函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,後者用於同步本進程的不同線程。後者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最後一個默認屬性。pthread_mutex_lock聲明開始用互斥鎖上鎖,此後的代碼直至調用pthread_mutex_unlock爲止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define THREAD_NUMBER 3
#define REPEAT_NUMBER 5
#define DELAY_TIME_LEVELS 10.0
pthread_mutex_t mutex;
void *thrd_func(void *arg)
{
int thrd_num = (int)arg;
int delay_time = 0;
int count = 0;
int res;
res = pthread_mutex_lock(&mutex); //互斥鎖上鎖
if(res)
{
printf("Thread %d lock failed/n",thrd_num);
pthread_exit(NULL);
}
printf("Thread %d is starting/n",thrd_num);
for(count = 0;count < REPEAT_NUMBER;count++)
{
delay_time = (int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
sleep(delay_time);
printf("/tThread %d:job %d delay = %d/n",thrd_num,count,delay_time);
}
printf("Thread %d finished/n",thrd_num);
pthread_exit(NULL);
}
int main()
{
pthread_t thread[THREAD_NUMBER];
int no = 0,res;
void *thrd_ret;
srand(time(NULL)); //隨機數種子
pthread_mutex_init(&mutex,NULL); //互斥鎖初始化
for(no = 0; no < THREAD_NUMBER;no++)
{
res = pthread_create(&thread[no],NULL,thrd_func,(void*)no);
if(res!=0)
{
printf("Create thread %d failed/n",no);
exit(res);
}
}
printf("Create threads success/n Waiting for threads to finish.../n");
for(no = 0;no<THREAD_NUMBER;no++)
{
res = pthread_join(thread[no],&thrd_ret);
if(!res)
{
printf("Thread %d joined/n",no);
}
else
{
printf("Thread %d join failed/n",no);
}
pthread_mutex_unlock(&mutex); //互斥鎖解鎖
}
pthread_mutex_destroy(&mutex);
return 0;
}
屬性結構爲pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問底的人可以自己去查看。屬性值不能直接設置,須使用相關函數進行操作,初始化的函數爲pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先級。默認的屬性爲非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優先級。
關於線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解爲內核線程,它位於用戶層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。默認狀況下,啓動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱爲非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因爲CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設置被綁定的輕進程的優先級和調度級可以使得綁定的線程滿足諸如實時反應之類的要求。
設置線程綁定狀態的函數爲pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指針,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。
線程的分離狀態決定一個線程以什麼樣的方式來終止自己。在上面的例子中,我們採用了線程的默認屬性,即爲非分離狀態,這種情況下,原有的線程等待創建的線程結束。只有當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<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define REPEAT_NUMBER 3
#define DELAY_TIME_LEVELS 10.0
int finish_flag = 0; //結束標誌變量
void *thrd_func(void *arg) //線程執行函數
{
int delay_time = 0;
int count = 0;
printf("Thread is starting/n");
for(count = 0;count < REPEAT_NUMBER;count++)
{
delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
sleep(delay_time);
printf("/tThread:job %d delay = %d/n",count,delay_time);
}
printf("Thread finished/n");
finish_flag = 1;
pthread_exit(NULL);
}
int main()
{
pthread_t thread;
pthread_attr_t attr;
int no = 0,res;
void *thrd_ret;
srand(time(NULL));
res = pthread_attr_init(&attr); //初始化線程屬性對象
if(res!=0)
{
printf("Create attribute failed/n");
exit(res);
}
res = pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM); //設置線程綁定屬性
res += pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //設置線程分離屬性
if(res!=0)
{
printf("Setting attribute failed/n");
exit(res);
}
res = pthread_create(&thread,&attr,thrd_func,NULL);
if(res!=0)
{
printf("Create thread failed/n");
exit(res);
}
pthread_attr_destroy(&attr); //釋放線程屬性對象
printf("Create tread success/n");
while(!finish_flag)
{
printf("Waiting for thread to finish.../n");
sleep(2);
}
return 0;
}