內核中並沒有線程的概念,只有PCB的概念,所以線程並沒有系統調用。
但是爲了使用戶操作線程方便,就有大佬封裝了一個POSIX線程庫,可以使我們在用戶層完成線程的創建銷燬、以及其他操作。
POSIX線程庫
- 使用時需要包含 <pthread.h> 頭文件
- 鏈接這些線程庫時,需要加上編譯命令 “-lpthread”選項
創建線程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*star t_routine)(void*), void *arg);
參數
thread:返回線程ID
attr:設置線程的屬性,
attr爲NULL表⽰使⽤默認屬性
start_routine:是個函數地址,線程啓動後要執⾏的函數 //線程的入口函數,線程一創建出來就去執行這個函數,主線程的入口函數是main
arg:傳給線程入口函數的參數
返回值:成功返回0;失敗返回錯誤碼
代碼演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void* ThreadEntry(void* arg)
{
(void)arg;
while(1)
{
printf("New Thread\n");
sleep(1);
}
return NULL;
}
int main()
{
//創建一個線程
pthread_t tid;
pthread_create(&tid,NULL,ThreadEntry,NULL);
//主線程進行死循環
while(1)
{
printf("Main Thread\n");
sleep(1);
}
return 0;
}
線程的執行順序不一定,由操作系統決定。
利用 pshack [進程id] 顯示當前進程中線程的調用炸
LWP 45128 : 作用在用戶態,幫助用戶控制線程
0x00007fxxxxxx :作用在內核態,協助調度
利用gdb調試線程
使用 gdb attach [進程id]
info thread 查看所有線程
以上 1,2的編號表示現在有線程的編號,*表示當前線程,這時用bt查看調用棧就是1的調用棧
thread [編號] 切換線程
線程終止
- 從線程 return,如果是從主線程return,相當於exit,所有線程都結束
- 線程可調用 pthread_exit終止自己
- 一個線程可以調用 pthread_cancel終止同一進程中的另一進程(參數傳 pthread_self()時終止自己)
//線程終止
void pthread_exit(void *value_ptr);
參數
value_ptr:value_ptr不要指向⼀個局部變量,線層入口函數的返回值(基本不用)。
返回值:⽆返回值,跟進程⼀樣,線程結束的時候⽆法返回到它的調⽤者(⾃⾝)
//取消一個執行中的線程
int pthread_cancel(pthread_t thread);
參數
thread:線程ID
返回值:成功返回0;失敗返回錯誤碼
線程等待與分離
線程等待
爲什麼要線程等待?
- 線程結束以後會有返回結果(線程入口函數返回的void*),我們需要讀取返回結果,回收資源,避免類似於殭屍進程的效果,這裏可理解爲內存泄漏
進程等待和線程等待
- 進程等待只可以父進程等子進程,一個線程則可以等待同組的任何一個線程
- 進程等待(阻塞、非阻塞),線程等待只有阻塞等待
int pthread_join(pthread_t thread, void **value_ptr);
參數
thread:線程ID
value_ptr:它指向⼀個指針,後者指向線程的返回值
一般使用pthread_join來保證線程的結束順序
返回值:成功返回0;失敗返回錯誤碼
代碼演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void* ThreadEntry1(void* arg)
{
(void)arg;
printf("Thread1\n");
return (void*)1;
}
void* ThreadEntry2(void* arg)
{
(void)arg;
printf("Thread2\n");
pthread_exit((void*)2);
return NULL;
}
void* ThreadEntry3(void* arg)
{
(void)arg;
//終止自己
printf("Thread3\n");
pthread_cancel(pthread_self());
return NULL;
}
int main()
{
pthread_t t1,t2,t3;
pthread_create(&t1,NULL,ThreadEntry1,NULL);
pthread_create(&t2,NULL,ThreadEntry2,NULL);
pthread_create(&t3,NULL,ThreadEntry3,NULL);
void* ret = NULL;
pthread_join(t1,&ret);//調用該函數的線程會掛起等待,直到t1線程終止
printf("Thread1 = %p\n",ret);
pthread_join(t2,&ret);
printf("Thread2 = %p\n",ret);
pthread_join(t3,&ret);
printf("Thread3 = %p\n",ret);
//3次pthread_join是串行的,必須t1結束以後,對1回收完畢以後纔會調用2
return 0;
}
在學習進程的時候我們也說進程父子進程的執行順序不一定,但是我們卻很難看到子進程先於父進程執行。
那爲什麼我們在這裏很容易可以看到線程執行順序不一定呢?
其實進程的執行順序確實是不一定的,但是每次創建一個進程的代價比較大,所以父進程就有很大的概率去執行,而線程的創建代價比較小,所以很容易看到執行順序不一定這個結果。
但是線層等待的順序一定是順序的,可以認爲三個等待函數時串行執行的,必須前面的執行完後面的纔可以執行。
代碼驗證線程共享棧、堆全局區的情況
線程分離
- 在默認情況下創建的線程是joinable的,線程退出後,要對其進行pthread_join操作,否則無法回收資源,造成內存泄漏。
- 如果不關心線程的返回值,那麼join就是一個負擔,這個是後可以告訴系統線程退出時,由操作系統自動回收資源。
int pthread_detach(pthread_t tid);
線程也可以對自己進行線程分離
pthread_detach(pthread——self());