Linux線程

線程

線程概念

在這裏插入圖片描述
進程: 傳統操作系統上的進程是pcb,操作系統通過pcb控制程序運行
線程: 但是Linux下線程使用pcb實現,Linux下pcb是線程,也叫輕量級進程(相較於傳統進程更加輕量化— 同一個進程中的線程公用同一個虛擬地址空間);進程是線程組
進程是資源分配的基本單位—資源是分配給整個線程組的
線程是cpu調度的基本單位—cpu調度通過pcb調度程序的運行

線程之間的獨有與共享:

獨有 共享
虛擬地址空間(代碼段、數據段):mm_struct
寄存器 文件描述符表:files_struct
信號屏蔽字 信號處理方式:sighand_struct
errno 當前工作路徑
線程標識符 用戶ID,組ID

線程和進程優缺點對比及使用場景:

優點:

  • 同一個進程中的線程共享同一個虛擬地址空間
  • 線程間通信更加方便
  • 線程的創建與銷燬相比於進程成本更低
  • 線程的調度成本要更低一點
  • 線程的執行力度要更加細緻

缺點:

  • 線程間缺乏訪問控制,編碼難度更高
  • 線程健壯性更低
  • 性能損失

注意:除非對主程序安全性、穩定性要求特別高的程序使用多進程處理多任務;其它大多都是多線程

線程控制:

yum -y install man-pages  //我使用的centos7,無法使用man手冊就運行前面的命令,安裝支持posix函數的man手冊

操作系統並沒有提供創建線程的系統調用接口,因此大佬們封裝了一個線程的接口庫實現線程控制;意味着用戶創建線程都使用的庫函數(所以有時候我們說創建的線程是一個用戶態線程,但是在內核中對應有一個輕量級進程實現線程程序的調度)

線程創建:線程間不存在父子關係

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.

pthread_create   每個線程都有自己獨有的一塊空間,並且這塊空間在進程的虛擬地址空間中
  pcb->pid   pcb->tgid     tid
  LWP      PID
輕量級進程id   進程ID   線程空間首地址

進程ID-線程組ID–tgid等於主線程的pid
在這裏插入圖片描述

  • 線程是庫函數創建的,如果用戶想要操作線程就必須擁有線程的操作句柄,而這個tid是線程地址空間的首地址,線程地址空間中保存了線程的數據,用戶可以通過這個tid實現線程控制
  • 每個線程都有自己的獨立空間,並且這些空間也是在進程的虛擬地址空間內
/*線程創建:                                                      
 * int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*stact_routine) (void *), void *arg);
 *  thread:用於獲取被創建的新線程id
 *  attr:用於設置線程屬性,通常置NULL
 *  start_routine:線程入口函數
 *  arg:傳遞給線程的參數,作爲start_routine的實參傳入
 * 返回值:0    失敗:!=0-----errno*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thr_start(void *arg)
{
    while(1) {
        printf("i am normal thread---ptr:%s\n", (char*)arg);
        sleep(1);
    }
    return NULL;
}
int main (int argc, char *argv[])
{
    pthread_t tid;
    int ret;
    char *ptr = "leihoua~~~";
    ret = pthread_create(&tid, NULL, thr_start, (void*)ptr);
    if (ret != 0) {
        printf("thread create error\n");
        return -1;
    }
    
    while(1) {
        printf("i am main thread-----new thread id:%p\n", tid);
        sleep(1);
    }
    return 0;
}

線程終止:進程退出

在任意線程中不能使用exit退出,因爲exit退出的也是整個進程
在普通線程中使用return退出(在 main函數中不能使用return退出,因爲退出的是進程)

pthread_exit     主動退出
int pthread_cancel(pthread_t thread)   取消線程,退出的線程被動取消 thread:要取消的線程id

線程退出也是會稱爲(殭屍進程)的

線程等待:

等待指定的線程退出–獲取退出線程的返回值,允許操作系統回收退出線程的資源

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.

一個資源創建成功後,默認有一個屬性是joinable屬性,處於joinable屬性的線程退出後,不會自動回收資源,需要被其他線程等待
只有處於joinable屬性的線程才能被等待,也必須被等待,因爲這種線程退出後資源不會自動回收

線程分離:

#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.

分離一個線程,將線程的屬性從joinable設置爲detach屬性;

處於detach屬性的線程,退出後,自動回收資源;
處於detach屬性的線程,退出後,不需要被等待;
分離一個線程的前提就是用戶對線程的退出返回值,根本不關心;

pthread_detach
線程分離可以是任意線程在任意時刻進行分離(線程一旦被創建直接分離;線程入口函數中分離自己)


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *thr_start(void *arg)
{
    //pthread_self(void)
    //  獲取調用線程ID
    pthread_detach(pthread_self());
    sleep(5);
    pthread_exit("nihaoa~~");
    while(1) {
        printf("i am ordinary thread\n");
        sleep(1);
        //void pthread_exit(void *retval);
        //退出調用線程,誰調用,誰退出
        //retval:   線程退出返回值
        //pthread_exit(NULL);
    }
    return NULL;
}
int main (int argc, char *argv[])
{
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, thr_start, NULL);
    if (ret != 0) {
        printf("thread create error\n");
        return -1;
    }
    //int pthread_cancel(pthread_t thread);
    //  取消線程,退出的線程被動取消
    //  thread:    要取消的線程id
    //pthread_cancel(tid);
    
    //int pthread_join(pthread_t thread, void **retval);
    //  阻塞等待指定線程退出
    //  thread:    線程id
    //  retval:    用於獲取線程退出返回值
    
    //int pthread_detach(pthread_t thread);
    //  分離指定的線程
    pthread_detach(tid);

    char *ptr = NULL;
    pthread_join(tid, (void**)&ptr);
    printf("ordinary thread exit val:%s\n", ptr);
    while(1) {
        printf("i am main thread\n");
        sleep(1);
    }
    return 0;
}

線程安全:

多個線程是否對同一個臨界資源進行了不受保護的非原子操作
線程安全就是多個線程對臨界資源進行操作,而不會造成數據二義性,反之是不安全

如何實現線程安全(同步與互斥)

同步: 臨界資源訪問的時序可控—保證訪問的合理性
互斥: 臨界資源同一時間的唯一訪問—保證訪問的安全性

如何實現互斥

互斥鎖mutex

模擬一個黃牛買票程序
原理: 原子操作的一個只具有0/1的計數器
互斥鎖使用步驟:
pthread_mutex_t     定義互斥鎖變量
pthread_mutex_init    初始化互斥鎖
pthread_mutex_lock    加鎖
pthread_mutex_unlock   解鎖
pthread_mutex_destroy   銷燬互斥鎖

死鎖:

因爲多方搶奪鎖資源,但是因爲搶奪推進順序不當引起死鎖導致相互僵持,產生程序無法推進卡死
產生必要條件:

  • 互斥條件—指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。如果此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。(我加了鎖,別人就不能再加)
  • 不可剝奪條件—指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。(我加的鎖,別人不能解)
  • 請求與保持條件—指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。(拿到第一個鎖後,去請求第二個鎖,但是第二個鎖獲取不到,第一個鎖也不釋放)
  • 環路等待條件—指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。
    在這裏插入圖片描述
    如何預防出現一個死鎖: 破壞必要條件
    如何避免/處理產生一個死鎖: 銀行家算法死鎖檢測算法

同步的實現:條件變量

同步的實現:等待與喚醒功能
條件不滿足則陷入等待,其它線程促使條件滿足後,喚醒等待
我想吃麪—>沒有面則需要等待做出來
廚師做面—>做出面後喚醒我的等待
模擬實現吃麪做面
在這裏插入圖片描述
在這裏插入圖片描述
pthread_cond_t     定義條件變量
pthread_cond_init    初始化條件變量
pthread_cond_wait     等待
pthread_cond_signal    喚醒
pthread_cond_destroy   銷燬條件變量

#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);//限時等待,超時後報錯返回
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);//阻塞死等
int pthread_cond_broadcast(pthread_cond_t *cond);//喚醒所有等待
int pthread_cond_signal(pthread_cond_t *cond);//至少喚醒一個等待
int pthread_cond_destroy(pthread_cond_t *cond);

cond:條件變量
attr:條件變量屬性,通常置NULL
mutex:互斥鎖
abstime:超時等待時間

條件變量是搭配互斥鎖一起使用的:

  1. 因爲條件變量實現同步只提供了等待與喚醒功能,並沒有提供條件判斷的功能,因此條件判斷需要用戶實現,但是條件的操作是一個臨界資源的操作,因此需要受保護,需要在條件判斷之前加鎖;
  2. 如果加鎖成功後,因爲條件不滿足而陷入休眠,就會導致卡死(另一方因爲無法獲取鎖,而導致無法促使條件滿足),因此需要在休眠之前解鎖,並且解鎖與休眠必須是原子操作
  3. 被喚醒之後,即將對臨界資源進行了操作(吃麪的人被喚醒就即將開始吃麪)。但是吃麪之前需要進行保護加鎖
  4. 所以pthread_cond_wait集合了三步原子操作:解鎖—>休眠等待—>被喚醒後加鎖

條件判斷需要循環判斷 —防止多次消費/多次生產導致數據二義邏輯混亂
不同的角色需要等待在不同的條件變量上 —防止角色喚醒錯誤導致阻塞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章