目錄
- 線程概要
- Linux內核線程實現原理
- 線程的共享/不共享資源
- 線程優缺點
- 線程控制原語
- pthread_self
- pthread_create
- pthread_exit
- pthread_join
- pthread_cancel
- 終止線程方式
- 控制原語對比
前情提要: Linux用戶級線程和內核級線程區別
線程概要
Linux內核線程實現原理
類Unix系統中,早期是沒有“線程”概念的,80年代才引入,借助進程機制實現出了線程的概念。因此在這類系統中,進程和線程關系密切。
輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone
從內核里看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三級頁表是相同的
進程可以蛻變成線程
線程可看做寄存器和棧的集合
在linux下,線程最是小的執行單位;進程是最小的分配資源單位
查看線程命令:ps -elf|grep thread
三級映射:進程PCB --> 頁目錄(可看成數組,首地址位于PCB中) --> 頁表 --> 物理頁面 --> 內存單元
線程的共享/不共享資源
線程共享資源 | 線程不共享資源 |
---|---|
文件描述符表 | 線程id |
每種信號的處理方式 | 處理器現場和棧指針(內核棧) |
當前工作目錄 | 獨立的棧空間(用戶空間棧) |
用戶ID和組ID | errno變量 |
內存地址空間(.text/.data/.bss/heap/共享庫) | 信號屏蔽字 |
調度優先級 |
線程優缺點
優點: 1. 提高程序并發性 2. 開銷小 3. 數據通信、共享數據方便
缺點: 1. 庫函數,不穩定 2. 調試、編寫困難、gdb不支持 3. 對信號支持不好
線程控制原語
pthread_self
獲取線程ID。其作用對應進程中 getpid() 函數。
? pthread_t pthread_self(void); 返回值:成功:0; 失敗:無!
? 線程ID:pthread_t類型,本質:在Linux下為無符號整數(%lu),其他系統中可能是結構體實現
? 線程ID是進程內部,識別標志。(兩個進程間,線程ID允許相同)
pthread_create
創建一個新線程。 其作用,對應進程中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:線程主函數執行期間所使用的參數。
練習:創建一個新線程,打印線程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;
}
線程默認共享數據段、代碼段等地址空間,常用的是全局變量,或者傳參形式。而進程不共享全局變量,只能借助mmap。
全局變量:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>int var = 100;void *tfn(void *arg)
{var = 200;printf("thread\n");return NULL;
}int main(void)
{printf("At first var = %d\n", var);pthread_t tid;pthread_create(&tid, NULL, tfn, NULL);sleep(1);printf("after pthread_create, var = %d\n", var);return 0;
}
傳參:
#include <func.h>void *tfn(void *arg){int* var = (int*)arg;*var = 200;printf("thread\n");return NULL;
}int main()
{int var = 100;printf("At first var = %d\n", var);pthread_t tid;pthread_create(&tid, NULL, tfn, &var);sleep(1);printf("after pthread_create, var = %d\n", var);return 0;
}
pthread_exit
作用:將單個線程退出
? void pthread_exit(void *retval); 參數:retval表示線程退出狀態,通常傳NULL
線程中,**禁止使用exit函數,會導致進程內所有線程全部退出**。所以,多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,將單個線程退出。任何線程里exit導致進程退出,其他線程未工作結束,主控線程退出時不能return或exit。
另注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。
pthread_join
阻塞等待線程退出,獲取線程退出狀態 其作用,對應進程中 waitpid() 函數。
? int pthread_join(pthread_t thread, void **retval); 成功:0;失敗:錯誤號
? 參數:thread:線程ID (【注意】:不是指針);retval:存儲線程結束狀態。
? 對比記憶:
? 進程中:main返回值、exit參數-->int;等待子進程結束 wait 函數參數-->int *
? 線程中:線程主函數返回值、pthread_exit-->void *;等待線程結束 pthread_join 函數參數-->void **
調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值。
如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD_CANCELED。
如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。
如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。
#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)); 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); //wait(&status);printf("a = %d, b = %d \n", retval->a, retval->b);return 0;
}
pthread_cancel
殺死(取消)線程 其作用,對應進程中 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。
終止線程的三種方法。注意“取消點”的概念。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>void *tfn1(void *arg)
{printf("thread 1 returning\n");return (void *)111;
}void *tfn2(void *arg)
{printf("thread 2 exiting\n");pthread_exit((void *)222);
}void *tfn3(void *arg)
{while (1) {//printf("thread 3: I'm going to die in 3 seconds ...\n");//sleep(1);pthread_testcancel(); //自己添加取消點*/}return (void *)666;
}int main(void)
{pthread_t tid;void *tret = NULL;pthread_create(&tid, NULL, tfn1, NULL);pthread_join(tid, &tret);printf("thread 1 exit code = %d\n\n", (int)tret);pthread_create(&tid, NULL, tfn2, NULL);pthread_join(tid, &tret);printf("thread 2 exit code = %d\n\n", (int)tret);pthread_create(&tid, NULL, tfn3, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, &tret);printf("thread 3 exit code = %d\n", (int)tret);return 0;
}
終止線程方式
總結:終止某個線程而不終止整個進程,有三種方法:
從線程主函數return。這種方法對主控線程不適用,從main函數return相當于調用exit。
一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
線程可以調用pthread_exit終止自己。
控制原語對比
? 進程 線程
? fork pthread_create
? exit pthread_exit
? wait pthread_join
? kill pthread_cancel
? getpid pthread_self 命名空間