天天看點

線程的建立和終止

一. 線程的建立

#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主動退出的說明。

繼續閱讀