天天看點

線程分析1、線程概念2、主要應用函數: 3、程序和線程函數對比: 4、函數分析 :  

1、線程概念

<1>、什麼是線程

LWP:light weight process 輕量級的程序,本質仍是程序(在Linux環境下)

程序:獨立位址空間,擁有PCB

線程:也有PCB,但沒有獨立的位址空間(共享)

差別:在于是否共享位址空間。 獨居(程序);合租(線程)。                                     

線程分析1、線程概念2、主要應用函數: 3、程式和線程函數對比: 4、函數分析 :  

Linux下: 線程:最小的執行機關

                 程序:最小配置設定資源機關,可看成是隻有一個線程的程序。

<2>、線程共享資源

1.檔案描述符表

2.每種信号的處理方式

3.目前工作目錄

4.使用者ID群組ID

5.記憶體位址空間 (.text/.data/.bss/heap/共享庫) (是以線程共享全局變量,程序不能共享全局變量)

<3>、線程非共享資源

1.線程id

2.處理器現場和棧指針(核心棧)

3.獨立的棧空間(使用者空間棧)

4.errno變量

5.信号屏蔽字

6.排程優先級

<4>、線程優、缺點

優點: 1. 提高程式并發性

            2. 開銷小

            3. 資料通信、共享資料友善

缺點: 1. 庫函數,不穩定 (程序是調用函數屬于系統調用,線程是調用庫函數)

           2. 調試、編寫困難、gdb不支援 (gdb誕生比線程早)

           3. 對信号支援不好(信号是一種古老的機制,線程是一種新機制)

優點相對突出,缺點均不是硬傷。Linux下由于實作方法導緻程序、線程差别不是很大。

突出特點:

  • 線程之間的通信更友善,同一程序下的線程共享全局變量、靜态變量等資料,而程序之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程式的難點。
  • 但是多程序程式更健壯,多線程程式隻要有一個線程死掉,整個程序也死掉了,而一個程序死掉并不會對另外一個程序造成影響,因為程序有自己獨立的位址空間。

2、主要應用函數: 

     [1]、pthread_self()函數           功能:擷取線程ID。其作用對應程序中 getpid() 函數。

     [2]、pthread_create()函數      功能:建立一個新線程。 其作用,對應程序中fork() 函數

     [3] 、pthread_join()函數         功能:阻塞等待線程退出,擷取線程退出狀态

     [4] 、pthread_detach函數      功能:實作線程分離

     [5] 、pthread_exit()函數           功能:将單個線程退出 

     [6] 、pthread_cancel函數      功能:殺死(取消)線程

     [7] 、pthread_equal函數        功能:比較兩個線程ID是否相等。

3、程序和線程函數對比: 

程序                           線程

fork                           pthread_create             功能:建立

exit                           pthread_exit                 功能:退出

wait                          pthread_join                 功能:等待回收

kill                            pthread_cancel            功能:殺死

getpid                       pthread_self                功能:擷取ID

4、函數分析 :  

 <1>、擷取線程ID,其作用對應程序中 getpid() 函數

pthread_t   pthread_self(void);

傳回值:成功:0; 失敗:無!

線程ID:pthread_t類型,本質:在Linux下為無符号整數(%lu),其他系統中可能是結構體實作

線程ID是程序内部,識别标志。(兩個程序間,線程ID允許相同)

注意:不應使用全局變量 pthread_t tid,在子線程中通過pthread_create傳出參數來擷取線程ID,而應使用pthread_self。

 <2>、建立一個新線程。 其作用,對應程序中fork() 函數。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);                                    

傳回值:成功:0; 失敗:錯誤号 -----Linux環境下,所有線程特點,失敗均直接傳回錯誤号。

pthread_t:目前Linux中可了解為:typedef  unsigned long int  pthread_t;

參數1:傳出參數,儲存系統為我們配置設定好的線程ID

參數2:通常傳NULL,表示使用線程預設屬性。若想使用具體屬性也可以修改該參數。

參數3:函數指針,指向線程主函數(線程體),該函數運作結束,則線程結束。

參數4:線程主函數執行期間所使用的參數。

【練習1】:建立一個新線程,列印線程ID。主線程列印程序ID     注意:連結線程庫 -lpthread  

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *tfn(void *arg)
{
	printf("I'm thread, Thread_ID = %lu\n", pthread_self());
	return NULL;
}

int main(void)
{
	pthread_t tid;

	pthread_create(&tid, NULL, tfn, NULL);
	sleep(1);
	printf("I am main, my pid = %d\n", getpid());

	return 0;
}
           

 運作結果:

線程分析1、線程概念2、主要應用函數: 3、程式和線程函數對比: 4、函數分析 :  

【練習2】:循環建立多個線程,每個線程列印自己是第幾個被建立的線程。(類似于程序循環建立子程序)  

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *tfn(void *arg)
{
	int i;

	i = (int)arg; //通過i來差別每個線程
	sleep(i);	
	printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());

	return NULL;
}

int main(int argc, char *argv[])
{
	int n = 5, i;
	pthread_t tid;


	for (i = 0; i < n; i++) 
	{
		pthread_create(&tid, NULL, tfn, (void *)i);
		
	}
	sleep(n);
	printf("I am main, and I am not a process, I'm a thread!\n" 
			"main_thread_ID = %lu\n", pthread_self());

	return 0;
}
           

 運作結果:

線程分析1、線程概念2、主要應用函數: 3、程式和線程函數對比: 4、函數分析 :  

  <3>、阻塞等待線程退出,擷取線程退出狀态 其作用,對應程序中 waitpid() 函數

int pthread_join(pthread_t thread, void **retval);

傳回值:成功:0;失敗:錯誤号

參數:thread:線程ID (【注意】:不是指針);

           retval:存儲線程結束狀态。

對比記憶:

程序中:main傳回值、exit參數-->int;等待子程序結束 wait 函數參數-->int *

線程中:線程主函數傳回值、pthread_exit-->void *;等待線程結束 pthread_join 函數參數-->void **

參數 retval 非空用法

調用該函數的線程将挂起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀态是不同的,總結如下:

  1. 如果thread線程通過return傳回,retval所指向的單元裡存放的是thread線程函數的傳回值。
  2. 如果thread線程被别的線程調用pthread_cancel異常終止掉,retval所指向的單元裡存放的是常數PTHREAD_CANCELED。
  3. 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。

如果對thread線程的終止狀态不感興趣,可以傳NULL給retval參數。

 【練習1】:pthread_join():實作等待線程退出,回收線程,并擷取線程退出資訊

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
//定義一個退出時用到的結構體資料類型
typedef struct 
{
	int a;
	int b;
} exit_t;

void *tfn(void *arg)
{
	exit_t *ret;//定義結構體指針
	ret = malloc(sizeof(exit_t));//為結構體指針配置設定記憶體,(隻要定義了指針,就要配置設定記憶體防止野指針,同時使用完還得通過free釋放記憶體) 

	ret->a = 100;
	ret->b = 300;

	pthread_exit((void *)ret);//退出線程,帶回退出時的狀态資料
}

int main(void)
{
	pthread_t tid;
	exit_t *retval;

	pthread_create(&tid, NULL, tfn, NULL);

	/*調用pthread_join可以擷取線程的退出狀态*/
	pthread_join(tid, (void **)&retval);      //注意(void **)&retval的用法,套路
	printf("a = %d, b = %d \n", retval->a, retval->b);

	return 0;
}

           

運作結果:

線程分析1、線程概念2、主要應用函數: 3、程式和線程函數對比: 4、函數分析 :  

  <4>、實作線程分離

 int pthread_detach(pthread_t thread);

傳回值:成功:0;失敗:錯誤号

線程分離狀态:指定該狀态,線程主動與主要線程斷開關系。線程結束後,其退出狀态不由其他線程擷取,而直接自己自動釋放。網絡、多線程伺服器常用。

線程的分離狀态決定一個線程以什麼樣的方式來終止自己。

1、非分離狀态:線程的預設屬性是非分離狀态,這種情況下,原有的線程等待建立的線程結束。隻有當pthread_join()函數傳回時,建立的線程才算終止,才能釋放自己占用的系統資源。

2、分離狀态:分離線程沒有被其他的線程所等待,自己運作結束了,線程也就終止了,馬上釋放系統資源。應該根據自己的需要,選擇适當的分離狀态。

也可使用 pthread_create函數參2(線程屬性)來設定線程分離。

      一般情況下,線程終止後,其終止狀态一直保留到其它線程調用pthread_join擷取它的狀态為止。但是線程也可以被置為detach狀态,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀态。不能對一個已經處于detach狀态的線程調用pthread_join,這樣的調用将傳回EINVAL錯誤。也就是說,如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

   <5>、将單個線程退出

 void  pthread_exit(void *retval);

參數:retval表示線程退出狀态,通常傳NULL

思考:使用exit将指定線程退出,可以嗎? 

結論:線程中,禁止使用exit函數,會導緻程序内所有線程全部退出。

在不添加sleep控制輸出順序的情況下。pthread_create在循環中,幾乎瞬間建立5個線程,但隻有第1個線程有機會輸出(或者第2個也有,也可能沒有,取決于核心排程)如果第3個線程執行了exit,将整個程序退出了,是以全部線程退出了。

是以,多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,将單個線程退出。任何線程裡exit導緻程序退出,其他線程未工作結束,主要線程退出時不能return或exit。

另注意,pthread_exit或者return傳回的指針所指向的記憶體單元必須是全局的或者是用malloc配置設定的,不能線上程函數的棧上配置設定,因為當其它線程得到這個傳回指針時線程函數已經退出了。

總結exit、return、pthread_exit各自退出效果。

return:傳回到調用者那裡去。

pthread_exit():将傳回到調用該函數的線程   (return和pthread_exit() 退出線程效果一樣)

exit: 将程序退出。

 【練習1】:pthread_exit():實作線程退出

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *tfn(void *arg)
{
	int i;

	i = (int)arg; //強轉。

	if (i == 2)//如果是第三個線程,則會退出線程,退出狀态為NULL
		pthread_exit(NULL);//return;  效果一樣
	
	sleep(i);	 //通過i來差別每個線程

	printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());

	return NULL;
}

int main(int argc, char *argv[])
{
	int n = 5, i;
	pthread_t tid;



	for (i = 0; i < n; i++) 
	{
		pthread_create(&tid, NULL, tfn, (void *)i);
		//将i轉換為指針,在tfn中再強轉回整形。
	}

	sleep(n);
	printf("I am main, I'm a thread!\n" 
			"main_thread_ID = %lu\n", pthread_self());

	return 0;
}

           

運作結果: 

線程分析1、線程概念2、主要應用函數: 3、程式和線程函數對比: 4、函數分析 :  

<6>、殺死(取消)線程 其作用,對應程序中 kill() 函數。

int pthread_cancel(pthread_t thread);

傳回值:成功:0;失敗:錯誤号 

【注意】:線程的取消并不是實時的,而有一定的延時。需要等待線程到達某個取消點(檢查點)。

類似于玩遊戲存檔,必須到達指定的場所(存檔點,如:客棧、倉庫、城裡等)才能存儲進度。殺死線程也不是立刻就能完成,必須要到達取消點。

取消點:是線程檢查是否被取消,并按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write..... 執行指令man 7 pthreads可以檢視具備這些取消點的系統調用清單。也可參閱 APUE.12.7 取消選項小節。

可粗略認為一個系統調用(進入核心)即為一個取消點。如線程中沒有取消點,可以通過調用pthreestcancel函數自行設定一個取消點。

被取消的線程, 退出值定義在Linux的pthread庫中。常數PTHREAD_CANCELED的值是-1。可在頭檔案pthread.h中找到它的定義:#define PTHREAD_CANCELED ((void *) -1)。是以當我們對一個已經被取消的線程使用pthread_join回收時,得到的傳回值為-1。

終止線程方式

總結:終止某個線程而不終止整個程序,有三種方法:

  1. 從線程主函數return。這種方法對主要線程不适用,從main函數return相當于調用exit。
  2. 一個線程可以調用pthread_cancel終止同一程序中的另一個線程。
  3. 線程可以調用pthread_exit終止自己。

 <7>、比較兩個線程ID是否相等。

int pthread_equal(pthread_t t1, pthread_t t2);