多線程編程-線程控制與屬性(結合與分離)

進程在各自獨立的地址空間中運行,進程之間共享數據需要用mmap或者進程間通信機制,而當需要在一個進程中同時執行多個控制流程併發執行多個任務時,多線程即爲出現。


在linux下,一個進程中的多線程都在進程的地址空間中運行,它的控制流程可以長期並存,操作系統會在各線程之間調度和切換:特點如下

1.線程都在當前進程的地址空間中運行,如圖:


創建一個進程需要創建自己的地址空間,創建頁表,並且建立虛擬地址與頁表與物理內存之間的映射關係等,而每個線程只是執行進程中某一部分代碼,在其進程地址空間運行,資源共享,只需要建立簡單的PCB描述其相關信息,而無需再分配相關資源,建立地址空間等一系列操作。所以線程(性能高,資源小)

2.線程是系統調度的基本單位(由於CPU只看pcb,無關其爲進程或線程,而線程又是進程模擬的(特點5));

3.進程是承擔分配系統資源的基本單位(操作系統以進程需求分配資源,線程共享其進程資源);

4.linux下進程稱爲輕量級進程;

5.linux下無真正意義上線程,都爲進程模擬的


由於同一進程的多個線程共享同一地址空間,因此Text Segment、Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一 個全局變量,在各線程中都可以訪問到(所以線程間通信較爲簡單,如直接定義全局變量即可實現),以下爲線程所共享的進程資源與環境:

1. 文件描述符表;
2. 每種信號的處理方式(SIG_IGN、SIG_DFL或者自定義的信號處理函數);
3. 當前工作目錄;
4. 用戶id和組id。

線程私有的資源:

1. 線程id;

2. 上下文,包括各種寄存器的值、程序計數器和棧指針;
3. 棧空間;
4. errno變量;
5. 信號屏蔽字;
6. 調度優先級。


線程庫函數是由POSIX標準定義的,稱爲POSIX thread或者pthread;在Linux上線程函數位於libpthread共享庫中,因此在編譯時要加上-lpthread選項。

線程控制:

1.創建線程:

函數:

int phread_create(phread_t* thread,const pthread_attr_t* attr,void* (*start_voutine)(void*),void* arg);
參數:

thread: 調用此函數後返回的子線程id;

attr: 表示此線程屬性(此處默認爲null);

start_voutine: 在一個線程中調用pthread_create()創建新的線程後,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給函數指針start_routine決 定。start_routine的返回值類型也是void *,這個指針的含義同樣由調用者自己定義;

arg: start_routine函數接收一個參數,是通pthread_create的arg參數傳遞給它的,該參數的類型爲void *,這個指針按什麼類型解釋由調用者自己定義。


2.線程等待:若線程屬性都爲可結合的,則目標線程必須阻塞等待子線程,否則可能造成類似殭屍進程的情況

函數:

int pthread_join(pthread_t thread,void** retval)
參數:

thread:等待目標線程的id;

retval:輸出型參數,保存目標線程函數執行完後的退出碼,三種情況:

1. 如果thread線程通過return返回,retval所指向的單元裏存放的是thread線程函數的返回值。
2. 如果thread線程被別的線程調用pthread_cancel異常終掉,retval所指向的單元裏存放的是常數PTHREAD_CANCELED==-1。
3. 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數,若不需要thread線程的終止狀態,可以傳NULL給retval參數。

3.線程終止:由上2可知有三種情況

1. 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit;
2. 一個線程可以調用pthread_cancel終止同一進程中的另一個線程;

函數:

int pthreas_cancel(pthread_t thread)
參數:

thread:線程id

3. 線程可以調用pthread_exit終止自己;

函數:

void pthread_exit(void* retval)
參數:

retval:retval是void *類型,和線程函數return返回值的用法一樣,其它線程可以調用pthread_join獲得這個指針.


當一個線程異常退出時,則會導致進程退出,所有其它線程也會退出,由於線程屬於進程,一個線程發生異常,操作系統則會給當前進程發送信號,終止此進程,此進程退出,所有資源被回收,其它線程必定也全部退出,所以多線程下的穩定性較差。


線程屬性:

默認創建線程是可結合的(joinable),一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲器資源(例如棧)是不釋放的,所以主線程必須等待子線程回收其狀態。

相反,若一個線程變爲可分離的,這個線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。所以它此時與主線程關係不大,但噹噹前進程退出時,該分離線程也會退出,因爲雖然其已分離,但其資源依然爲當前進程的,進程退出資源回收,其必定也隨之退出。

結合線程變爲分離線程方法:

函數:

int pthread_detach(pthread_self());   //自己改變自己屬性
int pthread_detach(pthread_t thread); //父線程改變其屬性
參數:

pthread_self():爲一個函數,獲取當前線程的id;

當一個線程被置爲可分離狀態,主線程不需再去等待,此時主線程不予干預此子線程的任何事情。


代碼實現:

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

void* thread_func(void* arg)   //子線程執行函數 
{
	pthread_t tid=pthread_self();  //獲取當前自己線程的id 

	//pthread_detach(tid);          //自己請求成爲分離線程 

	/*while(1)
	{
		sleep(1);
		printf("I am child thread!!!\n");
		printf("thread:tid=%u\n",tid);
	}*/
	
	printf("I am child thread!!!\n");
	printf("thread:tid=%u\n",tid);

	pthread_exit((int*)1);            //1.線程終止,調用函數使當前線程函數的返回值(void*)爲(int*)1 
	//return (int*)1;                 //2.直接 return返回 
}
int main()
{
	pthread_t tid;
	if(pthread_create(&tid,NULL,thread_func,NULL)<0)   //創建子線程:參數依次:線程ID,線程屬性,
	{                                                  //子線程執行函數地址(函數指針),前一個函數的傳參參數 
		perror("pthread_create");
		return -1;
	}
	
	//創建多線程 
	/*pthread_t tid1;
	pthread_t tid2;
	pthread_t tid3;
	pthread_create(&tid1,NULL,thread_func,NULL);
	pthread_create(&tid2,NULL,thread_func,NULL);
	pthread_create(&tid3,NULL,thread_func,NULL);*/

	printf("I am main thread!!!\n");
	printf("main:tid=%u thread:tid=%u\n",pthread_self(),tid);
	
	/*sleep(4);
	if(pthread_cancel(tid)==0)          //取消某一個爲tid子線程,將會此子線程執行函數的返回值(void*)變爲-1 
	{
		printf("main cancel thread success!!!\n");
	}*/

	sleep(4);
	pthread_detach(tid);       //主線程提出與爲tid的子線程分離 :分離之後主線程將不阻塞等待子線程執行完成,只執行自己任務,完成後直接返回 

	void* ret;
	int code=pthread_join(tid,&ret);  //線程等待:參數依次:等待子線程id,子線程所執行的函數的返回值(輸出型參數); 
	if(code==0)                       //返回值成功爲0,失敗錯誤碼 
	{
		printf("wait success:\n");
		printf("code=%d:%s  ret=%d\n",code,strerror(code),ret);
	}
	
	/*void* ret1;
	void* ret2;
	void* ret3;
	pthread_join(tid1,&ret1);
	pthread_join(tid2,&ret2);
	pthread_join(tid3,&ret3);*/

	return 0;
}
運行結果如下:

1.當創建線程,終止並等待後結果:



2.當子線程函數無限循環時,主線程調用pthread_cancel()函數時,結果:


當沒調用分離函數時主線程都等待成功後退出,當分別調用兩個分離函數後,結果:


主線程sleep(4)秒後退出,由於此時在main函數中,所以退出後當前進程退出,所以子線程中無限循環也隨之退出。

4.當在主線程下創建多個(此處爲4個)線程,並都執行同一個線程函數(有無線循環),結果如下:


查看當前進程中線程如下:




其中LWP代表輕量級線程,操作系統以此爲調度單位,並且它們都在同一個進程下運行,PID都爲3485.



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