Linux - 線程控制

內核中並沒有線程的概念,只有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());

joinable和線程分離是兩個對立的狀態,兩者不可能同時存在。
發佈了77 篇原創文章 · 獲贊 50 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章