Linux多線程總結

一、線程的理解

1、線程其實是一個進程的一個執行流。

2、線程是操作系統調度的基本單位,進程是承擔分配系統資源的基本單位。

3、linux下,一個進程就是一個獨佔資源的線程,即在這個地址空間僅有一個執行流,linux下的進程爲輕量級進程(進程可以理解爲是線程,可以理解爲linux下均爲線程),進程和線程均叫做pcb。

4、在一個進程中各線程還共享以下進程資源和環境:

1)文件描述符表

2)每種信號的處理方式式(SIG_IGN、 SIG_DFL或者定義的信號處理函數)

3)當前工作目錄

4)用戶id和組id

5、在一個進程中各線程各有一份:

1)線程id

2)上下文,包括各種寄存器的值、程序計數器和棧指針

3)棧空間

4)errno變量

5)信號屏蔽字
6.)調度優先級

  在Linux上線程函數位於libpthread共享庫中,因此在編譯時要加上-lpthread選項。

二、線程控制

1、創建線程

wKioL1nlox3Cq64TAABZzjru9To871.png

  

  返回值:成功返回0,失敗返回錯誤號。以前學過的系統函數都是成功返回0,失敗返回-1,而錯誤號保存在全局變量errno中,而pthread庫的函數都是通過返回值返回錯誤號,雖然每個線程也都有一個errno,但這是爲了兼容其它函數接口而提供的,pthread庫本身並不使用它,通過返回值返回錯誤碼更加清晰。

  在一個線程中調用pthread_create()創建新的線程後,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針start_routine決定。start_routine函數接收一個參數,是通過pthread_create的arg參數傳遞給它的,該參數的類型爲void *,這個指針按什麼類型解釋由調用者自己定義。start_routine的返回值類型也是void *,這個指針的含義同樣由調用者自己定義。start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到函數start_routine的返回值,類似於父進程調用wait(2)得到子進程的退出狀態。

  pthread_create成功返回後,新創建的線程的id被填寫到thread參數所指向的內存單元。調用getpid(2)可以獲得當前進程的id,是一個正整數值。線程id的類型是thread_t,它只在當前進程中保證是唯一的,在不同的系統中thread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印,調用pthread_self(3)可以獲得當前線程的id。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_t tid;
void* thread_run(void *val)
{
	printf("%s : pid is :%d,tid is : %u\n",
			(char*)val,(int)getpid(),
			(unsigned long long)pthread_self());
	return NULL;
}
int main()
{
	int err = pthread_create(&tid,NULL,thread_run,
			"other thread run");
	if( err != 0 ){
		printf("create thread error!info is :%s\n",
				strerror(err));
	}
	printf("main thread run : pid is :%d,tid is : %u\n",
			(int)getpid(),(unsigned long long)pthread_self());
	sleep(1);
	return 0;
}


運行結果:

wKioL1nlrAvhE9EeAABfoW_MVEo589.png

     可知在Linux上,thread_t類型是一個地址值,屬於同一進程的多個線程調用getpid(2)可以得到相同的進程號,而調用pthread_self(3)得到的線程號各不相同。
  由於pthread_create的錯誤碼不保存在errno中,因此不能直接用perror(3)打印錯誤信息,可以先用strerror(3)把錯誤碼轉換成錯誤信息再打印。
  如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止,由於從main函數return也相當於調用exit,爲了防止新創建的線程還沒有得到執行就終止,我們在main函數return之前延時1秒,這只是一種權宜之計,即使主線程等待1秒,內核也不一定會調度新創建的線程執行。

2、終止線程

如果需要只終止某個線程而不終止整個進程,可以有三種方法:
1) 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用。

2)一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
3)線程可以調用pthread_exit終止自己。

需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因爲當其它線程得到這個返回指針時線程函數已經退出了。

3、線程等待

wKioL1nlwOiA5nT6AABIdAGFGQ4567.png

     返回值:成功返回0,失敗返回錯誤號
  調用該函數的線程將掛起等待,直到id爲thread的線程終止。 thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
1)如果thread線程通過return返回,value_ptr所指向的單元裏存放的是thread線程函數的返回值。
2)如果thread線程被別的線程調用pthread_cancel異常終掉,value_ptr所指向的單元裏存放的是常數PTHREAD_CANCELED。
3)如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread1(void *val) 
{
	printf("thread 1 returning...\n");
	return (void*)1;
}
void *thread2(void *val)
{
	printf("thread 2 exiting...\n");
	pthread_exit((void*)2);
}
void *thread3(void *val)
{
	while(1){
		printf("pthread 3 is running,wait for be cancal...\n");
		sleep(1);
	}
	return NULL;
}

int main()
{
	pthread_t tid;
	void *tret;
	pthread_create(&tid,NULL,thread1,NULL);
	pthread_join(tid,&tret);
	printf("thread1 return,thread1 id is:%u,return code is:%d\n",
			(unsigned long)tid,(int)tret);
	pthread_create(&tid,NULL,thread2,NULL);
	pthread_join(tid,&tret);
	printf("thread2 exit,thread2 id is:%u,exit code is:%d\n",
			(unsigned long)tid,(int)tret);
    pthread_create(&tid,NULL,thread3,NULL);
	sleep(4);
	pthread_cancel(tid);
	pthread_join(tid,&tret);
	printf("thread3 return,thread3 id is:%u,cancal code is:%d\n",
			(unsigned long)tid,(int)tret);
	return 0;
}


wKioL1nlwf3xqs-lAADE6CA6Hcc794.png

      可見在Linux的pthread庫中常數PTHREAD_CANCELED的值是-1。可以在頭文件pthread.h中找到它的定義。

  一般情況下,線程終止後,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態爲止。 但是線程也可以被置爲detach狀態,這樣的線程一旦終止就立刻回收它佔用的所有資源,而不保留終止狀態。不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。 對一個尚未detach的線程調用pthread_join或pthread_detach都可以把該線程置爲detach狀態,也就是說,不能對同一線程調用兩次pthread_join,或者如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

4、線程分離

  在任何一個時間點上, 線程是可結合的(joinable)或者是分離的(detached) 。 一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲器資源(例如棧)是不釋放的。 相反, 一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。

  默認情況下,線程被創建成可結合的。 爲了避免存儲器泄漏,每個可結合線程都應該要麼被顯示地回收,即調用pthread_join;要麼通過調用pthread_detach函數被分離。如果一個可結合線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收,所以創建線程者應該調用pthread_join來等待線程運行結束,並可得到線程的退出代碼,回收其資源。
  由於調用pthread_join後,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。例如,在Web服務器中當主線程爲每個新來的連接請求創建一個子線程進行處理的時候,主線程並不希望因爲調用pthread_join而阻塞(因爲還要繼續處理之後到來的連接請求),這時可以在子線程中加入代碼:
pthread_detach(pthread_self())
或者父線程調用
pthread_detach(thread_id)(非阻塞,可立即返回)
這將該子線程的狀態設置爲分離的(detached),如此一來,該線程運行結束後會自動釋放所有源。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread(void *val)
{
	pthread_detach(pthread_self());
	printf("%s\n",(char *)val);
	return NULL;
}
int main()
{
	pthread_t tid;
	int tret = pthread_create(&tid,NULL,thread,"thread run...");
	if( tret != 0 ){
		printf("create error!,info:%s\n",strerror(tret));
		return tret;
	}
	int ret = 0;
	sleep(1);
	if( 0 == pthread_join(tid,NULL) ){
		printf("pthread wait success!\n");
		ret = 0;
	}
	else{
		printf("pthread wait failed!\n");
		ret = 1;
	}
	return ret;
}

wKiom1nlz72C1W9TAABGYidXUHI519.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章