Linux多線程總結

1、線程的理解linux

一、線程實際上是一個進程的一個執行流。服務器

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

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

四、在一個進程中各線程還共享如下進程資源和環境:spa

1)文件描述符表操作系統

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

3)當前工做目錄3d

4)用戶id和組id指針

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

1)線程id

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

3)棧空間

4)errno變量

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

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

2、線程控制

一、建立線程

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秒,內核也不必定會調度新建立的線程執行。

二、終止線程

若是須要只終止某個線程而不終止整個進程,能夠有三種方法:
1) 從線程函數return。這種方法對主線程不適用,從main函數return至關於調用。

2)一個線程能夠調用pthread_cancel終止同一進程中的另外一個線程。
3)線程能夠調用pthread_exit終止本身。

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

三、線程等待

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了。

四、線程分離

  在任何一個時間點上, 線程是可結合的(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

相關文章
相關標籤/搜索