一. 線程的建立
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start rtn)(void*), void *restrict arg);
傳回:成功傳回0,否則傳回錯誤編号
參數:tidp:線程辨別符指針;(存放所建立線程的辨別符的位址)
attr:線程屬性指針;
start_rtn:線程運作函數的起始位址;
arg:傳遞給線程運作函數的參數;
注:不能保證新線程和調用線程的執行順序(執行順序由排程算法決定);
下面給出一個具體的案例來說明線程的建立過程。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
//定義線程運作函數
void* th_fn(void *arg)
{
int distance = (int)arg;
int i;
for(i = 1; i <= distance; ++i)
{
printf("%lx run %d\n", pthread_self(), i);
int time = (int)(drand48() * 100000);// 随機睡眠一定的時間
usleep(time); // 微妙
}
// return (void*)0;
return (void*)distance;
}
int main(void)
{
int err;
pthread_t rabbit, turtle; // 定義線程辨別符
// 建立rabbit線程
// 第二個參數是線程的屬性
// 第三個參數是線程運作函數的起始位址
if((err = pthread_create(&rabbit, NULL,
th_fn, (void*)50)) != 0)
{
perror("pthread_create error");
}
//建立turtle線程
if((err == pthread_create(&turtle, NULL,
th_fn, (void*)50)) != 0)
{
perror("pthread_create error");
}
//主要線程調用pthread_join(),自己會阻塞
//直到rabbit線程和turtle線程結束方可運作
// pthread_join(rabbit, NULL);
// pthread_join(turtle, NULL);
//sleep(10); // 主要線程運a行
int result;
pthread_join(rabbit, (void*)&result);
printf("rabbit race distance is: %d\n", result);
pthread_join(turtle, (void*)&result);
printf("turtle race distance is: %d\n", result);
printf("race is finished\n");
printf("control thread id: %lx\n", pthread_self());
printf("finished\n");
return 0;
}
該案例是一個簡易的龜兔賽跑模型。
首先建立兩個線程rabbit和turtle。在pthread_create中,第四個參數時傳遞給線程運作函數得參數。第三個參數是線程運作函數th_fn,這裡我們列印出二者所跑的路程,每跑一步都進行一次睡眠。
說明:a. 在程式中,主要線程調用pthread_join()函數後自己會阻塞,rabbit線程和turtle線程運作結束主要線程方可運作;
b. pthread_join函數中的第二個參數存儲的是線程運作函數的傳回結果;
當然,如果線上程運作函數中我們想要輸出更多的内容,也就是參數中包含更多的内容,我們可以将這些内容封裝在一個結構體變量中,然後傳遞給函數,下面的例子對上例做了稍微的改變。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct
{
char name[20]; //存儲線程的名字
int time; //線程睡眠時間
int start;
int end;
}RaceArg;
void* th_fn(void *arg)
{
RaceArg *r = (RaceArg*)arg;
int i = r->start;
for(; i <= r->end; ++i)
{
printf("%s(%lx) running %d\n",
r->name, pthread_self(), i);
usleep(r->time);
}
//return (void*)0;//主動終止
//pthread_exit((void*)0);
return (void*)(r->end - r->start);
}
int main(void)
{
int err;
pthread_t rabbit, turtle;
RaceArg r_a = {"rabbit", (int)(drand48()*100000000), 20, 50};
RaceArg t_a = {"turtle", (int)(drand48()*100000000), 10, 60};
if((err = pthread_create(&rabbit, NULL,
th_fn, (void*)&r_a)) != 0)
{
perror("pthread_create error");
}
if((err = pthread_create(&turtle, NULL,
th_fn, (void*)&t_a)) != 0)
{
perror("pthread_create error");
}
//主要線程調用pthrea_join,自己阻塞
//等待其他線程運作結束自己再運作
// pthread_join(rabbit, NULL);
// pthread_join(turtle, NULL);
int result;
pthread_join(rabbit, (void*)&result);
printf("rabbit race distance is %d\n", result);
pthread_join(turtle, (void*)&result);
printf("turtle race distance is %d\n", result);
printf("race finished\n");
printf("control thread id: %lx\n", pthread_self());
printf("finished.\n");
return 0;
}
在該例子中,我們将線程的名字、每一步的睡眠時間、起始和終止點都封裝在了一個結構體變量中,線上程運作函數中,我們又将該結構體變量作為參數傳遞給線程運作函數,并将其輸出,最後線程運作函數傳回的是總的路程。
注意:在pthread_create中,第四個參數就不再是一個數了,而是一個包含很多内容的結構體變量。同樣,這裡需要主要線程進行阻塞。
問題:那麼在上述的案例中,兩個線程之間會公用一些資源,這期間會不會互相幹擾?
我們知道,每個線程都有自己獨立的棧空間,像線程運作函數中的一些局部變量就存儲在這些空間中。當然不同的線程也有一些共享的資源,比如上圖中的資料段部分,該空間存儲着全局變量和靜态變量,由各個線程共享,顯然這樣是不安全的,因為一個線程對某一個變量做了修改之後,另一個線程通路該變量時是修改後的。是以,在多線程程式設計中建議盡量使用局部變量。
二. 線程終止
1. 線程終止方式
- 主動終止:線程的執行函數中調用return語句或者調用pthread_exit()函數;
- 被動終止:線程可以被同一程序的其他線程取消,其他線程調用pthread_cancel(pthid);
#include <pthread.h>
int pthread_cancel(pthread_t pid);
void pthread_exit(void *retval);
int pthread_join(pthread_t th, void **thread_return);
//傳回值:成功傳回0,否則傳回錯誤編号;
函數解釋: pthread_cancel:線程可以被同一程序的其他線程取消,tid為被終止的線程辨別符; pthread_exit:1)retval:pthread_exit調用者線程的傳回值,可由其他函數和pthread_join來檢測擷取; 2)線程退出時使用函數pthread_exit,是線程的主動行為; 3)由一個線程中的多個線程共享資料段,是以通常線上程退出後,退出線程所占用的資源并不會随線程的結束而釋放。所有需 要pthread_join函數來等待線程結束,類似于wait系統調用; pthread_join:1)th:被等待線程的辨別符; 2)thread_return:使用者定義指針,用來存儲被等待線程的傳回值;
補充:pthread_join函數的作用? pthread_join使一個線程等待另一個線程結束。 代碼中如果沒有pthread_join,主線程會很快結束進而使整個程序結束,進而使建立的線程沒有機會開始執行就結束了。加入pthread_join後,主線程會一直等待,知道等待的線程結束自己才結束,使建立的線程有機會執行。
下面給出一個簡單示例:
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
typedef struct
{
int d1;
int d2;
}Arg;
void* th_fn(void *arg)
{
Arg *r = (Arg*)arg;
//return (void*)(r->d1 + r->d2);]
return (void*)r;
}
int main(void)
{
int err;
pthread_t th;
Arg r = {20, 50};
if((err = pthread_create(&th, NULL,
th_fn, (void*)&r)) != 0)
{
perror("pthread_create error");
}
/*
int *result;
pthread_join(th, (void**)&result);//第二個參數獲得子線程的傳回結果
printf("result is %d\n", (int)result);//這裡做了強制轉換
*/
/*
int result;
pthread_join(th, void(*)&result);
printf("result is %d\n", result);//這裡就不需要強制轉換
*/
int *result;
pthread_join(th, (void**)&result);
printf("result is %d\n",
((Arg*)result)->d1 + ((Arg*)result)->d2);
return 0;
}
該示例就是一個簡單的加法,不再作詳細的解釋了。 在上面線程建立的第二個程式中,線上程運作函數中分别有return和pthread_exit主動退出的說明。