Linux系統編程——線程(1)

前情提要: Linux用戶級線程和內核級線程區別linux

線程概要

Linux內核線程實現原理

類Unix系統中,早期是沒有「線程」概念的,80年代才引入,藉助進程機制實現出了線程的概念。所以在這類系統中,進程和線程關係密切。數組

  1. 輕量級進程(light-weight process),也有PCB,建立線程使用的底層函數和進程同樣,都是clone多線程

  2. 從內核裏看進程和線程是同樣的,都有各自不一樣的PCB,可是PCB中指向內存資源的三級頁表是相同的併發

  3. 進程能夠蛻變成線程函數

  4. 線程可看作寄存器和棧的集合線程

  5. 在linux下,線程最是小的執行單位;進程是最小的分配資源單位3d

查看線程命令:ps -elf|grep thread指針

1556722663332

三級映射:進程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;
}

1556723666858

傳參:

#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;
}

1556723909222


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獲得的終止狀態是不一樣的,總結以下:

  1. 若是thread線程經過return返回,retval所指向的單元裏存放的是thread線程函數的返回值。

  2. 若是thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元裏存放的是常數PTHREAD_CANCELED。

  3. 若是thread線程是本身調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。

  4. 若是對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;
}

1556724875987


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;
}

1556725457481

終止線程方式

總結:終止某個線程而不終止整個進程,有三種方法:

  1. 從線程主函數return。這種方法對主控線程不適用,從main函數return至關於調用exit。

  2. 一個線程能夠調用pthread_cancel終止同一進程中的另外一個線程。

  3. 線程能夠調用pthread_exit終止本身。


控制原語對比

​ 進程 線程

​ fork pthread_create

​ exit pthread_exit

​ wait pthread_join

​ kill pthread_cancel

​ getpid pthread_self 命名空間

相關文章
相關標籤/搜索