linux-執行緒

KHY666發表於2020-11-25

linux-執行緒

在這裡插入圖片描述

- 執行緒的概念

1>linux的執行緒與windos的執行緒區別很大(win的執行緒比較完善,程式不行,linux的程式發展的更早)

2>每一個程式都會分配0-4G的虛擬空間地址,而執行緒共享0-4G的虛擬地址空間,但每一個執行緒有自己不同的pcb.但pcb中指向記憶體資源的三級頁表是相同的
在這裡插入圖片描述三級頁表(mmu對映虛擬地址到實體記憶體的過程)
在這裡插入圖片描述

3>執行緒是最小的執行單位(搶佔cpu的最小單位(根據執行緒的LWP號(使用ps -Lf pid//檢視指定的執行緒LWP號))),程式是最小的資源分配單位(0-4G資源分配).
4>程式可以蛻變成執行緒
5>執行緒可看做是暫存器和棧的集合

- 執行緒之間共享的資源和非共享的資源

1>執行緒共享的資源
a:檔案描述符表
b:每種訊號的處理方式(所以執行緒不建議使用訊號,共享處理,互相干擾)
c:當前工作目錄(因為建立執行緒需要程式,程式不變,多個執行緒自然共享工作目錄)
d:使用者ID和使用者組ID
e:記憶體地址空間(.text/.data/.bss/heap/共享庫)(除開了棧空間:因為棧空間儲存的每個執行緒的函式h和變數)
2>執行緒獨享的資源
a:執行緒ID
b:處理器現場和棧指標(核心棧)
c:獨立的棧空間(使用者空間棧)
d:errno變數
e:訊號遮蔽字(系統阻塞訊號集)
f:排程優先順序

- 執行緒與程式的對比

優點:1>程式的併發性更高(搶佔cpu的執行時間更強);2>開銷小(與程式相差不是特別大);3>資料通訊,共享資料方便;(因為工共享使用者區(出去棧空間))
缺點(都不是很突出的問題):1>執行緒多使用庫函式,相比系統函式不穩定;2>不支援gdb除錯;3>對訊號支援不友好;

- 執行緒控制函式

1>pthread_self()函式:相當於getpid();獲取當前執行緒的ID號(用於程式間識別不同的執行緒).(注意:不應使用pthread_create()函式的傳出引數獲取執行緒的ID號,而應使用該函式)
2>pthread_create()函式(相當於fork()函式)
注意:linux環境下,所有執行緒特點,失敗若有返回值,直接返回的是編號;(區別程式)
3>pthread_exit()函式(注意:exit();pthread_exit();return ;三者線上程程式設計中的使用)
a:pthread_exit:結束呼叫該函式的執行緒;
b:return:返回到呼叫函式的地方(上一級)
c:exit:結束程式,所有的執行緒也會結束(執行緒程式設計慎重使用)
4>pthread_join()函式(相當於wait()函式(阻塞等待回收))
注意:程式是父程式給子程式回收,但是執行緒可以不,子執行緒也可以呼叫該函式進行回收指定的執行緒.
5>pthread_detach()函式(執行緒獨有的)
將執行緒分離,分離後的執行緒結束後自動清理pcb控制部分的資源回收;常用於伺服器裡.分離後的執行緒就不需要pthread_join()函式回收(回收分離執行緒會報錯);
6>pthread_cancel()函式
注意:殺死(取消)執行緒,相當於程式的kill()函式,但是pthread_cancel()函式不是實時的,必須到達取消點(比如系統呼叫:pause,wait,等函式,man 7 pthreads檢視取消點)才會取消.

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

	/*******************************************************
	*函式:pthread_self() 
	*標頭檔案: #include <pthread.h>
	*格式: pthread_t pthread_self(void); //pthread_t:typedef unsigned long int型別
    *注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
    *作用:獲取當前程式的ID號
	*返回值:returning the calling thread's ID.
	********************************************************/
	
	/*******************************************************
	*函式:pthread_create() 
	*標頭檔案: #include <pthread.h>
	*格式:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
	*引數:pthread_t *thread:傳出引數;儲存系統分配的執行緒號;
	*const pthread_attr_t *attr:通常傳入NULL,表示使用執行緒的預設屬性.(可以通過此引數將建立的執行緒設定為遊離狀態(分離),執行緒棧空間大小的修改(容納更多的執行緒))
	*void *(*start_routine) (void *):函式指標(函式返回值為 void *,引數為 void *型別的),指向表示執行緒體的函式,該函式執行完,
	*新建立的執行緒就結束.void *arg:執行緒主體函式的引數;
    	*注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
   	 *作用:建立一個新的執行緒,主執行緒繼續向下執行,而子執行緒去執行start_routine函式的動作
	*返回值:On  success,  pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
	********************************************************/
		/*******************************************************
	*函式:pthread_detach() 
	*標頭檔案: #include <pthread.h>
	*格式:int pthread_detach(pthread_t thread);//pthread_t thread: pthread_create()第一個引數返回的執行緒號
    *注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
   	*作用:分離執行緒,執行緒結束後自動回收核心資源,不需要再次呼叫pthread_join()進行回收.
	*返回值:On  success,  pthread_detach() returns 0; on error, it returns an error number.
	********************************************************/
	/*******************************************************
	*函式:pthread_join() 
	*標頭檔案: #include <pthread.h>
	*格式:int pthread_join(pthread_t thread, void **retval);//pthread_t thread:pthread_create()函式建立執行緒時第一個引數傳出的ID值;
	*void **retval:接受執行緒的返回值.(注意型別轉換)
    *注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
   	*作用:阻塞回收指定子執行緒(相當於程式中的wait()函式)(在回收的時候可以傳遞引數出來)
	*返回值:On  success,  pthread_join()  returns  0; on error, it returns an error number.
	********************************************************/
	/*******************************************************
	*函式:pthread_exit() 
	*標頭檔案: #include <pthread.h>
	*格式:  void pthread_exit(void *retval);//void *retval:範型引數,無返回值
    *注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
    *作用:terminate calling thread(將呼叫該函式的執行緒結束)
	*返回值:無
	*區別: return:返回到呼叫函式的地方
	*		pthread_exit():將呼叫該函式的執行緒結束
	*		exit():結束程式,所有的執行緒也會結束(執行緒程式設計慎用)
	********************************************************/
	/*******************************************************
	*函式:pthread_cancel() 
	*標頭檔案: #include <pthread.h>
	*格式:int pthread_cancel(pthread_t thread);//pthread_t thread:pthread_create()函式建立執行緒時第一個引數傳出的ID值;
    *注意: Compile and link with -pthread(編譯和連結時加上-pthread引數)
   	*作用:殺死執行緒(相當於程式中的kill()函式)
	*返回值:On  success, pthread_cancel() returns 0; on error, it returns a nonzero error number.
	********************************************************/

int var=100;//區分執行緒,子執行緒進行更改,全域性也會更改,證明執行緒的全域性變數是共享的(程式是:讀時共享,寫時複製)
int i=1;
int k=1;
//用於pthread_join()函式回收使用
typedef struct exit_c{
	int sta;
	char c;
	int num;
}exit_t;
//子線主體函式
void * pthread_func( void *arg)
{	
	exit_t *retval=(exit_t*)arg;//再一次格式轉換	
	sleep(retval->num);
	var=1000;
	retval->c='M';
	retval->sta=retval->num+1;
	printf("%dth thread ID is:%lu,process ID id %d,var=%d\n",retval->num+1,pthread_self(),getpid(),var);
	pthread_exit((void *)retval);//這裡等同return 0;但是使用exit(0)會導致程式退出,所有執行緒也會退出.//注意(void *)retval型別轉換;
}

/*******************************************************
*驗證執行緒控制原語
*******************************************************/
int main()
{
	

	pthread_t tid[5]; //存放五個執行緒的tid
	int ret,ret1;
	exit_t *retval[5];
	//迴圈建立5個執行緒,區別程式(執行緒不用考慮子執行緒建立子執行緒的問題,如果要子執行緒建立子執行緒,需要在pthread_func()函式再次呼叫pthread_create()函式.)
	for(int i=0;i<5;i++)
	{	
		retval[i]=(exit_t*)malloc(sizeof(exit_t));
		retval[i]->num=i;
		ret=pthread_create(&tid[i],NULL,pthread_func,(void *)retval[i]);//注意:(void *)retval:retval是需要轉換型別的,進行的是傳值操作;如果這裡傳入retval,進行傳地址操作;
        //會導致錯誤(5個執行緒的產生很快完成,但是執行緒的主體函式不一定執行完,所以就會導致上一個執行緒與下一個執行緒的值相等,所以在傳地址的時候保證地址的唯一性;(建立一個執行緒就均可傳值\傳地址)
        //ret1=pthread_detach(tid[i]);//如果此處設定執行緒分離,後面就不用呼叫pthread_join()函式進行執行緒回收                                      	             	 	
		                                                   		 	 
		if(ret!=0)
		{
			 fprintf(stderr,"pthread_create error:%s\n",strerror(ret));  //char *strerror(int errnum);//該函式只需傳入錯誤號,然後就會輸出對應的錯誤描述(程式程式設計就會直接返回錯誤描述);int fprintf(FILE *stream, const char *format, ...);
			 exit(1);
		}
	}
	sleep(i);//保證每個執行緒都去執行執行緒主體函式

	//回收5個執行緒
	
	sleep(6);//保證子執行緒建立完畢
	for(int i=0;i<5;i++)
	{	
		
		pthread_join(tid[i],(void **)&retval[i]);//阻塞等待回收子執行緒;//(void **)&retval[i]一級指標轉為二級指標:先取地址,再轉為二級指標
		printf("**************************************************\n");
		printf("%dth thread was Recycled: sta:%d; c:%c; \n", retval[i]->num+1 ,retval[i]->sta,retval[i]->c);
		free(retval[i]);
	}
	printf("*******in mian thread ID is:%lu,process ID id %d,var=%d*********\n",pthread_self(),getpid(),var);
	
	pthread_exit(NULL);//主控執行緒結束,並不會導致程式ID號的改變// 這裡如果不加slepp(); return 0;和exit(0);都會是程式退出;
}

執行結果:
在這裡插入圖片描述

- 執行緒\程式控制函式對比

在這裡插入圖片描述注意:分離執行緒回收會報錯22(所以分離的執行緒不用回收),殺死的執行緒回收值為-1;(pthread_testcancel()函式是加入取消點的作用)

- 執行緒使用的注意事項

1>注意NPTL庫版本一致(使用命令檢視:getconf GNU_LIBPTHREAD_VERSION)(GNU提供執行緒庫,程式設計平臺不一樣時需要保證該庫的版本一致)
2>編譯的時候加上 -pthread (引入執行緒庫)
3>主執行緒退出,其他執行緒不退出時應該呼叫pthread_exit(),如果用其他的(return exit)導致程式退出,全部執行緒也會退出
4>避免殭屍執行緒:1:pthread_join()回收執行緒;2:pthread_detach()函式分離執行緒;3:pthread_creat()函式第二個引數指定建立的執行緒為遊離態;
5>各個執行緒共享堆區,所以malloc()和mmap分配的空間可以區其他執行緒釋放;
6>避免線上程裡使用fork()函式
7>避免線上程裡引入訊號機制;

相關文章