天天看點

Linux多線程操作pthread_t程序概念線程概念線程程序基本操作

程序概念

程序是表示資源配置設定的基本機關,又是排程運作的基本機關。例如,使用者運作自己的程式,系統就建立一個程序,并為它配置設定資源,包括各種表格、記憶體空間、磁盤空間、I/O裝置等。然後,把該程序放人程序的就緒隊列。程序排程程式選中它,為它配置設定CPU以及其它有關資源,該程序才真正運作。是以,程序是系統中的并發執行的機關。

在Mac、Windows NT等采用微核心結構的作業系統中,程序的功能發生了變化:它隻是資源配置設定的基本機關,而不再是排程運作的機關。在微核心系統中,真正排程運作的基本機關是線程。是以,實作并發功能的機關是線程。

線程概念

線程是程序中執行運算的最小機關,亦即執行處理機排程的基本機關。如果把程序了解為在邏輯上作業系統所完成的任務,那麼線程表示完成該任務的許多可能的子任務之一。線程可以在處理器上獨立排程執行,這樣,在多處理器環境下就允許幾個線程各自在單獨處理器上進行。作業系統提供線程就是為了友善而有效地實作這種并發性。

相對程序而言,線程是一個更加接近于執行體的概念,它可以與同程序中的其他線程共享資料,但擁有自己的棧空間,擁有獨立的執行序列。在串行程式基礎上引入線程和程序是為了提高程式的并發度,進而提高程式運作效率和響應時間。

引入線程的好處

(1)易于排程。

(2)提高并發性。通過線程可友善有效地實作并發性。程序可建立多個線程來執行同一程式的不同部分。

(3)開銷少。建立線程比建立程序要快,所需開銷很少。。

(4)利于充分發揮多處理器的功能。通過建立多線程程序(即一個程序可具有兩個或更多個線程),每個線程在一個處理器上運作,進而實作應用程式的并發性,使每個處理器都得到充分運作。

程序:

子程序具備自己獨立的使用者空間(内容全部複制父程序);

父子程序不可互相通路對方資源;

線程:

僅申請自己的棧空間,與同程序的其它線程共享記憶體空間;

需要注意資源的同步和互斥通路問題

在Linux系統中,多線程的管理使用 pthread_t

線程程序基本操作

Linux多線程操作pthread_t程式概念線程概念線程程式基本操作
一、建立線程 pthread_create

int   pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

第一個參數為指向線程辨別符的指針,也就是線程對象的指針

第二個參數用來設定線程屬性。

第三個參數是線程運作函數的位址,通俗了解線程要執行函數(線程做的事情的)指針。一般這個函數執行時間比較長(有while循環),做的事情比較多。如果單次動作(執行時間比較短),也就無需多線程執行了。

最後一個參數是線程要運作函數的參數。

線程的預設堆棧大小是1MB,就是說,系統每建立一個線程就要至少提供1MB的記憶體,那麼,建立線程失敗,極有可能就是記憶體不夠用了。

pthread_create會導緻記憶體洩露! pthread_create建立的線程結束後,系統并未回收其資源,進而導緻了洩露。

為什麼要分離線程?

    線程的分離狀态決定一個線程以什麼樣的方式來終止自己。線程的預設屬性是非分離狀态,這種情況下,原有的線程等待建立的線程結束。隻有當pthread_join()函數傳回時,建立的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運作結束了,線程也就終止了,馬上釋放系統資源。程式員應該根據自己的需要,選擇适當的分離狀态。

從上面的描述中可以得知如果調用pthread_create函數建立一個預設非分離狀态的線程,如果不用pthread_join()函數,線程結束時并不算終止,是以仍然會占用系統資源。這裡有如下幾種方法解決這個問題:

如果新線程建立後,不用pthread_join()等待回收新線程,那麼就會造成記憶體洩漏,但是當等待新線程時,主線程就會一直阻塞,影響主線程處理其他連結要求,這時候就需要一種辦法讓新線程退出後,自己釋放所有資源,是以産生了線程分離。

1.使用pthread_join()函數回收相關記憶體區域。

int pthread_detach(pthread_t thread);将已經運作中的線程設定為分離狀态;

pthread_t tid;
void* state;
pthread_create(&tid, NULL, test, NULL);
pthread_join(tid, &state);
           

pthread_join使一個線程等待另一個線程結束。代碼中如果沒有pthread_join 主線程會很快結束進而使整個程序結束,進而使建立的線程沒有機會開始執行就結束了。加入pthread_join後,主線程會一直等待直到等待的線程結束自己才結束,使建立的線程有機會執行。pthread_join()函數以阻塞的方式等待thread指定的線程結束。當函數傳回時,被等待線程的資源被收回。如果線程已經結束,那麼該函數會立即傳回。并且thread指定的線程必須是joinable的。

2.可以調用 pthread_detach() 函數分離線程。

pthread_t tid;
pthread_create(&tid, NULL, test, NULL);
pthread_detach(tid);
           

當然,也可以在 thread function 中調用。

void* test(void* arg)

{

    .....

    pthread_detach(pthread_self());

    return NULL;

}

3.使用線程屬性 pthread_attr_setdetachstate

pthread_attr_t attr;
pthread_t tid;
 
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 
pthread_create(&tid, &attr, test, NULL);
 
sleep(3);//等待線程結束
 
pthread_attr_destroy(&attr);
           

二、線程屬性

pthread_create()中的attr參數是一個結構指針,結構中的元素分别對應着新線程的運作屬性,主要包括以下幾項:

typedef struct
{
    int     detachstate;     //線程的分離狀态
    int     schedpolicy;     //線程排程政策
    struct  sched_param schedparam;   //線程的排程參數
    int     inheritsched;    //線程的繼承性
    int     scope;           //線程的作用域
    size_t  guardsize;       //線程棧末尾的警戒緩沖區大小
    int     stackaddr_set;
    void *  stackaddr;       //線程棧的位置
    size_t  stacksize;       //線程棧的大小
}pthread_attr_t;
           

__detachstate, 表示新線程是否與程序中其他線程脫離同步,預設為 PTHREAD_CREATE_JOINABLE狀态。如果置位,則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。這個屬性也可以線上程建立并運作以後用pthread_detach()來設定,而一旦設定為 PTHREAD_CREATE_DETACH狀态(不論是建立時設定還是運作時設定)則不能再恢複到 PTHREAD_CREATE_JOINABLE狀态。

int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate)設定線程的分離狀态

int pthread_attr_getdetachstate (const pthread_attr_t *__attr,int *__detachstate)擷取線程參數中的分離狀态;

__schedpolicy,表示新線程的排程政策,主要包括 SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種,預設為SCHED_OTHER,後兩種排程政策僅對超級使用者有效。運作時可以用過 pthread_setschedparam()來改變。

int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy),設定線程的排程政策;

__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運作優先級。這個參數僅當排程政策為實時(即SCHED_RR 或SCHED_FIFO)時才有效,并可以在運作時通過pthread_setschedparam()函數來改變,預設為0。

int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr, struct sched_param *__restrict __param)

//擷取參數中的線程優先級;

int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr, const struct sched_param *__restrict __param)

//設定線程的優先級;

__inheritsched, 有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定排程政策和 排程參數(即attr中的值),而後者表示繼承調用者線程的值。預設為PTHREAD_EXPLICIT_SCHED

int pthread_attr_setinheritsched (pthread_attr_t *__attr, int __inherit)

//設定線程排程政策的繼承屬性,該函數必須在root權限下調用;

__scope, 表示線程間競争CPU的範圍,也就是說線程優先級的有效範圍。POSIX的标準中定義了兩個值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有線程一起競争CPU時間,後者表示僅與同 程序中的線程競争CPU。目前LinuxThreads僅實作了PTHREAD_SCOPE_SYSTEM一值。

int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope); 

//設定線程優先級的可競争範圍:

pthread_attr_t結構中還有一些值,但不使用pthread_create()來設定。

int pthread_attr_init (pthread_attr_t *__attr), 初始化pthread建立參數;

為了設定這些屬性,POSIX定義了一系列屬性設定函數,包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關的pthread_attr_get---/pthread_attr_set---函數。

三、線程終止

線程退出

void pthread_exit(void *value_ptr); //線上程執行的函數中調用此接口

例子:

void *thread_run(void* arg)
{
 
    while(1)
    {
        printf("new thread,thread is :%u,pid:%d\n",pthread_self(),getpid());
        sleep(1);
        pthread_exit(NULL);
    }
}
           

線程取消

int pthread_cancel(pthread_t thread);

參數

thread:線程ID

傳回值:成功傳回0;失敗傳回錯誤碼

例子:

int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,thread_run,NULL);
    while(1)
    {   
        printf("main thread,thread is :%u,pid:%d\n",pthread_self(),getpid())    ;
        sleep(3);
        pthread_cancel(pthread_self());//殺死自己,pthread_self()是擷取主線程id
    }
 
    return 0;
}
           

 int pthread_setcancelstate(int state, int *oldstate)

state: 欲設定的線程狀态,PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。預設值是PTHREAD_CANCEL_ENABLE,可以被取消。

oldstate: 存儲原來的線程狀态

  int pthread_setcanceltype(int type, int *oldtype)

type: 欲設定的類型,PTHREAD_CANCEL_DEFERRED:在取消點取消和PTHREAD_CANCEL_ASYNCHRONOUS:可随時執行新的或未決的請求。

oldtype: 存儲原來的類型

四、線程安全

多個線程同時操作臨界資源而不會出現資料二義性,實作線程安全:

互斥: 臨界資源同一時間唯一通路,是指某一資源同時隻允許一個通路者對其進行通路,具有唯一性和排它性,操作的獨占性。但互斥無法限制通路者對資源的通路順序,即通路是無序的。

同步: 臨界資源的合理通路,是指在互斥的基礎上(大多數情況),通過其它機制實作通路者對資源的有序通路。顯然,同步是一種更為複雜的互斥,而互斥是一種特殊的同步。

多線程的同步與互斥的差別

假如把整條道路看成是一個【程序】的話,那麼馬路中間白色虛線分隔開來的各個車道就是程序中的各個【線程】了

①這些線程(車道)共享了程序(道路)的公共資源(土地資源)。

②這些線程(車道)必須依賴于程序(道路),也就是說,線程不能脫離于程序而存在(就像離開了道路,車道也就沒有意義了)。

③這些線程(車道)之間可以并發執行(各個車道你走你的,我走我的),也可以互相同步(某些車道在交通燈亮時禁止繼續前行或轉彎,必須等待其它車道的車輛通行完畢)。

④這些線程(車道)之間依靠代碼邏輯(交通燈)來控制運作,一旦代碼邏輯控制有誤(死鎖,多個線程同時競争唯一資源),那麼線程将陷入混亂,無序之中。

⑤這些線程(車道)之間誰先運作是未知的,隻有線上程剛好被配置設定到CPU時間片(交通燈變化)的那一刻才能知道。

1.互斥

互斥如何實作:

互斥鎖: 一個1/0的計數器

1辨別完成加鎖,加鎖就是計數-1;

操作完畢後要解鎖, 解鎖就是計數+1

0表示不可以加鎖, 不能加鎖則等待

互斥鎖的操作步驟:

1.定義互斥鎖變量 pthread_mutex_t

pthread_mutex_t lock;

2.初始化互斥鎖變量 pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

3.加鎖 pthread_mutex_lock

int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞式加鎖

int pthread_mutex_lock(pthread_mutex_t* mutex)//阻塞式加鎖

如果一個線程既想獲得鎖,又不想挂起等待,可以調用pthread_mutex_trylock,如果Mutex已經被 另一個線程獲得,這個函數會失敗傳回EBUSY,而不會使線程挂起等待。

4.解鎖 pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t* mutex)//解鎖

5.銷毀互斥鎖 pthread_mutex_destory

int pthread_mutex_destroy(pthread_mutex_t* mutex);

死鎖::對所資源的競争以及程序/線程加鎖的推進順序不當,因為對一些無法加鎖的鎖進行加鎖而導緻程式卡死

死鎖産生的四個必要條件:

互斥條件(我能操作别人不能操作)

不可剝奪操作(我的鎖,别人不能解)

請求與保持條件(拿着碗裡的,看着鍋裡的)

環路等待條件

避免死鎖:破壞必要條件

死鎖處理:死鎖檢測算法 ,銀行家算法

2.同步

條件變量:描述某些資源就緒與否的狀态,為了實作同步而引入。同步是以互斥為前提的。少數情況可實作無鎖同步。

1.定義條件變量 pthread_cond_t

pthread_cond_t condition;

2.初始化條件變量 pthread_cond_init

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

參數: pthread_cond_attr是用來設定pthread_cond_t的屬性,當傳入的值是NULL的時候表示使用預設的屬性。

3.等待 / 喚醒 pthread_cond_wait / pthread_cond_signal

int pthread_cond_broadcast(pthread_cond_t* cond);//喚醒條件變量等待的所有線程

int pthread_cond_signal(pthread_cond_t* cond)//喚醒條件變量上等待的一個線程

int pthread_cond_wait(pthread_cond_t* cond)//解鎖互斥量并等待條件變量觸發

int pthread_cond_timewait(pthread_cond_t* cond,int abstime)//pthread_cond_wait,但可設定等待逾時 

4.銷毀條件變量 pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t* cond);

條件變量為什麼要搭配互斥鎖使用?

因為條件變量本身隻提供等待與喚醒的功能,具體要什麼時候等待需要使用者來進行判斷.這個條件的判斷,通常涉及臨界資源的操作(其他線程要通過修改條件,來促使條件滿足), 而這個臨界資源的操作應該受到保護.是以要搭配互斥鎖一起使用。

3.互斥和同步的聯合使用

pthread_mutex_lock(&mutex)
while or if(線程執行的條件是否成立)
    pthread_cond_wait(&cond,&mutex);
    線程執行
pthread_mutex_unlock(&mutex);
           

五、其他操作

程序id:

這裡所說的程序ID指我們通過fork建立子程序,子程序和父程序在核心中獨立運作,并且一個程序對應一個程序描述符(PCB),PCB中包含了程序的ID,通過getpid傳回目前程序ID

線程id:

核心态線程id:linux核心中,并不存線上程這一說,而是通過複制了程序的PCB作為辨別自己(線程),作為程序的一個執行分支;既然有程序描述符(PCB)辨別,自然就有一個辨別符(ID)來辨別着我是你(程序)的哪一個分支,這個辨別符(ID)就是核心中的線程ID,通過syscall獲得

#include <sys/syscall.h>

pid_t tid;

tid = syscall(SYS_gettid); //線上程執行的函數中調用此接口

使用者态線程id:對線程的操控是由使用者自己來完成,那麼對此線程操控,使用者知道你是哪一個線程,故此又有了使用者态的線程ID;這裡我們通過pthread_self()函數獲得。

 #include <pthread.h>

pthread_t pthread_self(void); //線上程執行的函數中調用此接口

傳回值:成功傳回0,失敗傳回錯誤碼

注:這裡的ID是一個位址,而不是向上面兩個ID是一個整數

對于單線程的程序,核心中tid==pid,對于多線程程序,他們有相同的pid,不同的tid。tid用于描述核心真實的pid和tid資訊。

pthread_self傳回的是posix定義的線程ID,man手冊明确說明了和核心線程tid不同。它隻是用來區分某個程序中不同的線程,當一個線程退出後,新建立的線程可以複用原來的id。

描述線程的id,為什麼需要兩個不同的ID呢?

答:這是因為線程庫實際上由兩部分組成:核心的線程支援+使用者态的庫支援(glibc),Linux在早期核心不支援線程的時候glibc就在庫中(使用者态)以纖程(就是使用者态線程)的方式支援多線程了,POSIX thread隻要求了使用者程式設計的調用接口對核心接口沒有要求。linux上的線程實作就是在核心支援的基礎上以POSIX thread的方式對外封裝了接口,是以才會有兩個ID的問題。

gettid 擷取的是核心中真實線程ID,  對于多線程程序來說,每個tid實際是不一樣的。

而pthread_self擷取的是相對于程序的線程控制塊的首位址, 隻是用來描述統一程序中的不同線程

Linux多線程操作pthread_t程式概念線程概念線程程式基本操作

————————————————

版權聲明:本文為CSDN部落客「銀冰冷月」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/sevens_0804/article/details/102823184

繼續閱讀