24-執行緒共享資源問題
1. 執行緒共享資源
如果說pthread_create函式跟fork函式是對應的,一個建立執行緒,一個建立程序。
但是程序呼叫fork建立程序的代價較高,即便是依靠寫時複製機制,仍然需要複製諸如記憶體頁表和檔案描述符表之類的多種程序屬性,fork呼叫的過程實際上非常複雜,這意味著fork呼叫在時間上的開銷不少(關於fork函式參考:18-用fork函式建立新程序)。
但執行緒解決了這個問題,執行緒之所以能夠方便,快速的共享資料,是因為程序呼叫fork建立子程序所需複製的諸多程序屬性線上程間本來就是共享的,無需複製記憶體頁,頁表等,這對執行緒來說,效率提高了不少。當然這是有代價的:不過這要避免出現多個執行緒同時修改同一份資料的情況,這需要使用執行緒同步機制(這裡暫時不討論同步問題)。
從圖1來看,由於同一程序的多個執行緒共享程序的資源,比如全域性記憶體(資料段和堆),除此之外還共享以下資源和環境:
程式碼文字段 開啟的檔案描述符 訊號處理函式 當前工作目錄 使用者id和組id 程序id和父程序id …
有些資源是每個執行緒各自獨有一份,非共享:
執行緒id 使用者空間棧 errno變數 訊號遮蔽字 排程優先順序 …
2. 執行緒共享實驗——全域性變數
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//全域性變數
int var = 100;
//執行緒主控函式
void *tfn(void *arg) {
//修改全域性變數var的值
var = 200;
printf(" create thread succesful\n");
return NULL;
}
int main(void) {
//主控執行緒第一次列印var
printf("before pthread_create var = %d\n", var);
pthread_t tid;
pthread_create(&tid, NULL, tfn, NULL);
sleep(1);
//主控執行緒再次列印var
printf("after pthread_create, var = %d\n", var);
return 0;
}
程式執行結果:
說明執行緒間是共享全域性變數的
3. 執行緒共享實驗——區域性變數
既然全域性變數是共享的,那麼區域性變數是不是也是共享的呢?為了驗證這個問題,我們來看實驗二
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//執行緒主控函式
void *tfn(void *arg) {
int i = (int)arg;
//區域性變數
int temp_val = 250;
if(i == 0){
//執行緒1修改了temp_val後,列印temp_val的值
temp_val = 100;
printf("pthread1 , temp_val = %d\n" , temp_val);
}else{
//執行緒2列印temp_val的值
printf("pthread2 , temp_val = %d\n" , temp_val);
}
printf(" create thread succesful\n");
return NULL;
}
int main(void) {
pthread_t tid[2];
int i;
for(i = 0; i < 2; i++){
pthread_create(&tid[i] , NULL , tfn , (void *)i);
}
sleep(1);
return 0;
}
程式執行結果:
區域性變數temp_val的初始值是250,在建立了2個執行緒,執行緒1修改了temp_val的值為100並列印,而執行緒2再訪問temp_val的值還是250,這說明執行緒間是不共享區域性變數的
。
問題來了,為什麼執行緒間不共享區域性變數,為了弄明白這個問題,我們還得回到圖1。
首先,執行緒的生命週期一般是線上程主函式中,當執行緒一建立就會在使用者棧開闢一塊空間給執行緒主函式使用,這意味著我們每建立一個執行緒都會在使用者棧開闢一塊空間給執行緒主函式使用。如圖2所示:執行緒1修改區域性變數temp_val的值為100,實際上修改的是執行緒1的棧裡的temp_val的值,並不會影響執行緒2的棧中的temp_val,所以執行緒2列印temp_val的值還是250,也就是說多執行緒不共享使用者棧。
4. 執行緒和程序的區別
另外之前在學習程序時,我們的理解是程序就是程式執行的執行體,而實際上程序一旦建立就自動包含了一個主執行緒,真正的執行體是主執行緒。
那麼程序是什麼?我們可以把程序理解為空間上的概念,它為所有的執行體(執行緒)提供必要的資源(記憶體、檔案描述符、程式碼等),而執行緒,是時間上的概念,它是抽象的、假想的、動態的指令執行過程。
可以把程序理解為學校或學校的各種資源,執行緒是學校裡的一個個學習的學生,學生共享學校裡的各種資源。
但是以上這些都是概念上的執行緒和程序的區別,如果我們要真正的本質區分程序和執行緒,就是看是否共享PCB程序控制塊,因為程序間PCB是各自獨立的,而執行緒間PCB是共享的。
因為執行緒間是共享全域性變數的,這說明了執行緒間的虛擬地址空間是一樣的,pthread_create函式底層也複製了一份一模一樣的PCB,所以說一個執行緒修改了資料空間的資料的話,其他執行緒都會因此受到影響。