天天看點

多線程建立、退出與回收

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;
}

           
多線程建立、退出與回收

本例程展示了如何利用線程建立函數的第四個參數向線程傳入資料,舉例了如何以位址的方式傳入值、以變量的方式傳入值,例程代碼的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先執行,因線程是無序執行,故加入該睡眠函數控制順序