1、線程概念
<1>、什麼是線程
LWP:light weight process 輕量級的程序,本質仍是程序(在Linux環境下)
程序:獨立位址空間,擁有PCB
線程:也有PCB,但沒有獨立的位址空間(共享)
差別:在于是否共享位址空間。 獨居(程序);合租(線程)。
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;
}
運作結果:
【練習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;
}
運作結果:
<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得到的終止狀态是不同的,總結如下:
- 如果thread線程通過return傳回,retval所指向的單元裡存放的是thread線程函數的傳回值。
- 如果thread線程被别的線程調用pthread_cancel異常終止掉,retval所指向的單元裡存放的是常數PTHREAD_CANCELED。
- 如果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;
}
運作結果:
<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;
}
運作結果:
<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。
終止線程方式
總結:終止某個線程而不終止整個程序,有三種方法:
- 從線程主函數return。這種方法對主要線程不适用,從main函數return相當于調用exit。
- 一個線程可以調用pthread_cancel終止同一程序中的另一個線程。
- 線程可以調用pthread_exit終止自己。
<7>、比較兩個線程ID是否相等。
int pthread_equal(pthread_t t1, pthread_t t2);