1.在程式中,可以通過函數,pthread_self,來傳回目前線程的線程号,例程1給出了列印線程tid号
擷取線程号
#include <pthread.h>
pthread_t pthread_self(void);
成功:傳回線程号
#include <stdio.h>
#include <pthread.h>
int main()
{
pthread_t tid = pthread_self();
printf("tid = %ld\n",tid);
return 0;
}
2.線程的建立
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void ), void arg);
成功:傳回0
該函數第一個參數為pthread_t類型的線程号位址,當函數執行成功後會指向建立線程的線程号;第二個參數表示了線程的屬性,一般傳入NULL表示預設屬性;第三個參數代表傳回值為void,形參為void的函數指針,當線程建立成功後,會自動的執行該回調函數;第四個參數則表示為向線程處理函數傳入的參數,若不傳入,可用NULL填充
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun(void *arg)
{
printf("pthread_New = %ld\n",pthread_self());
}
int main()
{
pthread_t tid1;
int ret = pthread_create(&tid1,NULL,fun,NULL);
if(ret != 0){
perror("pthread create");
return -1;
}
/*tid_main 為通過pthread_self擷取的線程ID,tid_new通過執行pthread_create成功後tid指向的空間*/
printf("tid_main = %ld tid_new = %ld \n",pthread_self(),tid1);
/*因線程執行順序随機,不加sleep可能導緻主線程先執行,導緻程序結束,無法執行到子線程*/
sleep(1);
return 0;
}
3.向線程傳入參數
pthread_create()的最後一個參數的為void類型的資料,表示可以向線程傳遞一個void資料類型的參數,線程的回調函數中可以擷取該參數
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
printf("%s: arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
}
void *fun2(void *arg)
{
printf("%s: arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
}
int main()
{
pthread_t tid1,tid2;
int a = 50;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&a); //建立線程傳入變量a的位址
if(ret != 0){
perror("pthread create");
return -1;
}
ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a); //建立線程傳入變量a的值
if(ret != 0){
perror("pthread create");
return -1;
}
sleep(1);
printf("%s: a = %d Add = %p\n",__FUNCTION__,a,&a);
return 0;
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL4kzN1EDM1kDM2ETMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
本例程展示了如何利用線程建立函數的第四個參數向線程傳入資料,舉例了如何以位址的方式傳入值、以變量的方式傳入值,例程代碼的21行,是将變量a先行取位址後,再次強制類型轉化為void後傳入線程,線程處理的回調函數中,先将萬能指針void轉化為int*,再次取位址就可以獲得該位址變量的值,其本質在于位址的傳遞。例程代碼的27行,直接将int類型的變量強制轉化為void進行傳遞(針對不同位數機器,指針對其字數不同,需要int轉化為long在轉指針,否則可能會發生警告),線上程處理回調函數中,直接将void資料轉化為int類型即可,本質上是在傳遞變量a的值。
上述兩種方法均可得到所要的值,但是要注意其本質,一個為位址傳遞,一個為值的傳遞。當變量發生改變時候,傳遞位址後,該位址所對應的變量也會發生改變,但傳入變量值的時候,即使位址指針所指的變量發生變化,但傳入的為變量值,不會受到指針的指向的影響,實際項目中切記兩者之間的差別。具體說明見例程4.
4.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
while(1){
printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
sleep(1);
}
}
void *fun2(void *arg)
{
while(1){
printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
int a = 50;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
if(ret != 0){
perror("pthread_create");
return -1;
}
sleep(1);
ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
if(ret != 0){
perror("pthread_create");
return -1;
}
while(1){
a++;
sleep(1);
printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);
}
return 0;
}
5.在處理實際項目中,往往會遇到傳遞多個參數的問題,我們可以通過結構體來進行傳遞,解決此問題
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
struct Stu{
int ld;
char Name[32];
float Mark;
};
void *fun1(void *arg)
{
struct Stu *tmp = (struct Stu *)arg;
printf("%s:ld = %d Name = %s Mark =%.2f\n",__FUNCTION__,tmp->ld,tmp->Name,tmp->Mark);
}
int main()
{
pthread_t tid1,tid2;
struct Stu stu;
stu.ld = 10000;
strcpy(stu.Name,"ZhangSan");
stu.Mark = 94.6;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
if(ret != 0){
perror("pthread_creat");
return -1;
}
printf("%s:ld = %d Name = %s Mark =%.2f\n",__FUNCTION__,stu.ld,stu.Name,stu.Mark);
sleep(1);
return 0;
}
6.線程的退出與回收
線程的退出情況有三種:第一種是程序結束,程序中所有的線程也會随之結束。第二種是通過函數pthread_exit來主動的退出線程。第三種通過函數pthread_cancel被其他線程被動結束。當線程結束後,主線程可以通過函數pthread_join/pthread_tryjoin_np來回收線程的資源,并且獲得線程結束後需要傳回的資料。
該函數為線程退出函數,在退出時候可以傳遞一個void*類型的資料帶給主線程,若選擇不傳出資料,可将參數填充為NULL。
該函數為線程回收函數,預設狀态為阻塞狀态,直到成功回收線程後被沖開阻塞。第一個參數為要回收線程的tid号,第二個參數為線程回收後接受線程傳出的資料。
該函數為非阻塞模式回收函數,通過傳回值判斷是否回收掉線程,成功回收則傳回0,其餘參數與pthread_join一緻。
該函數傳入一個tid号,會強制退出該tid所指向的線程,若成功執行會傳回0。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
static int tmp = 0; //必須要static修飾,否則pthread_join無法擷取到正确值
tmp = *(int *)arg;
tmp+=100;
printf("%s:Addr = %p tmp = %d\n",__FUNCTION__,&tmp,tmp);
pthread_exit((void *)&tmp);//将變量tmp取位址轉化為void*類型傳出
}
int main()
{
pthread_t tid1;
int a = 50;
void *Tmp = NULL;//因pthread_join第二個參數為void**類型
int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
if(ret != 0){
perror("pthread_create");
return -1;
}
pthread_join(tid1,&Tmp);
printf("%s:Addr = %p Val = %d\n",__FUNCTION__,Tmp,*(int *)Tmp);
return 0;
}
上述例程先通過23行将變量以位址的形式傳入線程,線上程中做出了自加100的操作,當線程退出的時候通過線程傳參,用void*類型的資料通過pthread_join接受。此例程去掉了之前加入的sleep函數,原因是pthread_join函數具備阻塞的特性,直至成功收回掉線程後才會沖破阻塞,是以不需要靠考慮主線程會執行到30行結束程序的情況。特别要說明的是例程第8行,當變量從線程傳出的時候,需要加static修飾,對生命周期做出延續,否則無法傳出正确的變量值。
7.
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun(void *arg)
{
printf("Pthread:%d Come !\n",(int )(long)arg+1);
pthread_exit(arg);
}
int main()
{
int ret,i,flag = 0;
void *Tmp = NULL;
pthread_t tid[3];
for(i = 0;i < 3;i++){
ret = pthread_create(&tid[i],NULL,fun,(void *)(long)i);
if(ret != 0){
perror("pthread_create");
return -1;
}
}
while(1){ //通過非阻塞方式收回線程,每次成功回收一個線程變量自增,直至3個線程全數回收
for(i = 0;i <3;i++){
if(pthread_tryjoin_np(tid[i],&Tmp) == 0){
printf("Pthread : %d exit !\n",(int )(long )Tmp+1);
flag++;
}
}
if(flag >= 3) break;
}
return 0;
}
例程7展示了如何使用非阻塞方式來回收線程,此外也展示了多個線程可以指向同一個回調函數的情況。例程6通過阻塞方式回收線程幾乎規定了線程回收的順序,若最先回收的線程未退出,則一直會被阻塞,導緻後續先退出的線程無法及時的回收。
通過函數pthread_tryjoin_np,使用非阻塞回收,線程可以根據退出先後順序自由的進行資源的回收。
8.
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg)
{
printf("Pthread:1 come!\n");
while(1){
sleep(1);
}
}
void *fun2(void *arg)
{
printf("Pthread:2 come!\n");
pthread_cancel((pthread_t )(long)arg);
pthread_exit(NULL);
}
int main()
{
int ret,i,flag = 0;
void *Tmp = NULL;
pthread_t tid[2];
ret = pthread_create(&tid[0],NULL,fun1,NULL);
if(ret != 0){
perror("pthread_create"); /殺死線程1,使之強制退出
return -1;
}
sleep(1);
ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]); //傳輸線程1的線程号
if(ret != 0){
perror("pthread_create");
return -1;
}
while(1){ //通過非阻塞方式收回線程,每次成功回收一個線程變量自增,直至2個線程全數回收
for(i = 0;i <2;i++){
if(pthread_tryjoin_np(tid[i],NULL) == 0){
printf("Pthread : %d exit !\n",i+1);
flag++;
}
}
if(flag >= 2) break;
}
return 0;
}
例程8展示了如何利用pthread_cancel函數主動的将某個線程結束。27行與33行建立了線程,将第一個線程的線程号傳參形式傳入了第二個線程。第一個的線程執行死循環睡眠邏輯,理論上除非程序結束,其永遠不會結束,但在第二個線程中調用了pthread_cancel函數,相當于向該線程發送一個退出的指令,導緻線程被退出,最終資源被非阻塞回收掉。此例程要注意第32行的sleep函數,一定要確定線程1先執行,因線程是無序執行,故加入該睡眠函數控制順序