1. 程式人生 > >Linux多執行緒程式設計入門

Linux多執行緒程式設計入門

1 執行緒基本知識

程序是資源管理的基本單元,而執行緒是系統排程的基本單元,執行緒是作業系統能夠進行排程運算的最小單位,它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。

一個程序在某一個時刻只能做一件事情,有了多個控制執行緒以後,在程式的設計成在某一個時刻能夠做不止一件事,每個執行緒處理獨自的任務。

需要注意的是:即使程式執行在單核處理器上,也能夠得到多執行緒程式設計模型的好處。處理器的數量並不影響程式結構,所以不管處理器個數多少,程式都可以通過執行緒得以簡化。

linux作業系統使用符合POSIX執行緒作為系統標準執行緒,該POSIX執行緒標準定義了一整套操作執行緒的API。

2. 執行緒標識

與程序有一個ID一樣,每個執行緒有一個執行緒ID,所不同的是,程序ID在整個系統中是唯一的,而執行緒是依附於程序的,其執行緒ID只有在所屬的程序中才有意義。執行緒ID用pthread_t表示。

//pthread_self直接返回呼叫執行緒的ID
include <pthread.h>
pthread_t pthread_self(void);

判斷兩個執行緒ID的大小是沒有任何意義的,但有時可能需要判斷兩個給定的執行緒ID是否相等,使用以下介面:

//pthread_equal如果t1和t2所指定的執行緒ID相同,返回0;否則返回非0值。
include
<pthread.h> int pthread_equal(pthread_t t1, pthread_t t2);

3. 執行緒建立

一個執行緒的生命週期起始於它被建立的那一刻,建立執行緒的介面:

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

函式引數:

thread(輸出引數),由pthread_create線上程建立成功後返回的執行緒控制代碼,該控制代碼在後續操作執行緒的API中用於標誌該新建的執行緒;
start_routine(輸入引數)

,新建執行緒的入口函式;
arg(輸入引數),傳遞給新執行緒入口函式的引數;
attr(輸入引數),指定新建執行緒的屬性,如執行緒棧大小等;如果值為NULL,表示使用系統預設屬性。

函式返回值:

成功,返回0;
失敗,返回相關錯誤碼。

需要注意:

  1. 主執行緒,這是一個程序的初始執行緒,其入口函式為main函式。
  2. 新執行緒的執行時機,一個執行緒被建立之後有可能不會被馬上執行,甚至,在建立它的執行緒結束後還沒被執行;也有可能新執行緒在當前執行緒從pthread_create前就已經在執行,甚至,在pthread_create前從當前執行緒返回前新執行緒就已經執行完畢。

程式例項:

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

void printids(const char *s){
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s, pid %lu tid %lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,
    (unsigned long)tid);
}

void *thread_func(void *arg){
    printids("new thread: ");
    return ((void*)0);
}
int main() {
    int err;
    pthread_t tid;
    err = pthread_create(&tid,NULL,thread_func,NULL);
    if (err != 0) {
        fprintf(stderr,"create thread fail.\n");
    exit(-1); 
    }
    printids("main thread:");
    sleep(1);   
    return 0;
}

注意上述的程式中,主執行緒休眠一秒,如果不休眠,則主執行緒不休眠,則其可能會退出,這樣新執行緒可能不會被執行,我自己註釋掉sleep函式,發現好多次才能讓新執行緒輸出。

編譯命令:

gcc -o thread thread.c -lpthread

執行結果如下:

main thread:, pid 889 tid 139846854309696 (0x7f30a212f740)
new thread: , pid 889 tid 139846845961984 (0x7f30a1939700)

可以看到兩個執行緒的程序ID是相同的。其共享程序中的資源。

4. 執行緒終止

執行緒的終止分兩種形式:被動終止和主動終止

被動終止有兩種方式:

  1. 執行緒所在程序終止,任意執行緒執行exit_Exit或者_exit函式,都會導致程序終止,從而導致依附於該程序的所有執行緒終止。
  2. 其他執行緒呼叫pthread_cancel請求取消該執行緒。

主動終止也有兩種方式:

  1. 線上程的入口函式中執行return語句,main函式(主執行緒入口函式)執行return語句會導致程序終止,從而導致依附於該程序的所有執行緒終止。
  2. 執行緒呼叫pthread_exit函式,main函式(主執行緒入口函式)呼叫pthread_exit函式, 主執行緒終止,但如果該程序內還有其他執行緒存在,程序會繼續存在,程序內其他執行緒繼續執行。

執行緒終止函式:

include <pthread.h>
void pthread_exit(void *retval);

執行緒呼叫pthread_exit函式會導致該呼叫執行緒終止,並且返回由retval指定的內容。
注意:retval不能指向該執行緒的棧空間,否則可能成為野指標!

5. 管理執行緒的終止

5.1 執行緒的連線

一個執行緒的終止對於另外一個執行緒而言是一種非同步的事件,有時我們想等待某個ID的執行緒終止了再去執行某些操作,pthread_join函式為我們提供了這種功能,該功能稱為執行緒的連線:

include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

引數說明:

thread(輸入引數),指定我們希望等待的執行緒
retval(輸出引數),我們等待的執行緒終止時的返回值,就是線上程入口函式中return的值或者呼叫pthread_exit函式的引數

返回值:

成功時,返回0
錯誤時,返回正數錯誤碼

當執行緒X連線執行緒Y時,如果執行緒Y仍在執行,則執行緒X會阻塞直到執行緒Y終止;如果執行緒Y在被連線之前已經終止了,那麼執行緒X的連線呼叫會立即返回。

連線執行緒其實還有另外一層意義,一個執行緒終止後,如果沒有人對它進行連線,那麼該終止執行緒佔用的資源,系統將無法回收,而該終止執行緒也會成為殭屍執行緒。因此,當我們去連線某個執行緒時,其實也是在告訴系統該終止執行緒的資源可以回收了。

注意:對於一個已經被連線過的執行緒再次執行連線操作, 將會導致無法預知的行為!

5.2 執行緒的分離

有時我們並不在乎某個執行緒是不是已經終止了,我們只是希望如果某個執行緒終止了,系統能自動回收掉該終止執行緒所佔用的資源。pthread_detach函式為我們提供了這個功能,該功能稱為執行緒的分離:

#include <pthread.h>
int pthread_detach(pthread_t thread);

預設情況下,一個執行緒終止了,是需要在被連線後系統才能回收其佔有的資源的。如果我們呼叫pthread_detach函式去分離某個執行緒,那麼該執行緒終止後系統將自動回收其資源。

/*
* 檔名: thread_sample1.c
* 描述:演示執行緒基本操作
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

/*子執行緒1入口函式*/
void *thread_routine1(void *arg)
{
    fprintf(stdout, "thread1: hello world!\n");
    sleep(1);
    /*子執行緒1在此退出*/
    return NULL;
}

/*子執行緒2入口函式*/
void *thread_routine2(void *arg)
{

    fprintf(stdout, "thread2: I'm running...\n");
    pthread_t main_thread = (pthread_t)arg;

    /*分離自我,不能再被連線*/
    pthread_detach(pthread_self());

    /*判斷主執行緒ID與子執行緒2ID是否相等*/
    if (!pthread_equal(main_thread, pthread_self())) {
        fprintf(stdout, "thread2: main thread id is not equal thread2\n");
    }

    /*等待主執行緒終止*/
    pthread_join(main_thread, NULL);
    fprintf(stdout, "thread2: main thread exit!\n");

    fprintf(stdout, "thread2: exit!\n");
    fprintf(stdout, "thread2: process exit!\n");
    /*子執行緒2在此終止,程序退出*/
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{

    /*建立子執行緒1*/
    pthread_t t1;
    if (pthread_create(&t1, NULL, thread_routine1, NULL)!=0) {
        fprintf(stderr, "create thread fail.\n");
        exit(-1);
    }
    /*等待子執行緒1終止*/
    pthread_join(t1, NULL);
    fprintf(stdout, "main thread: thread1 terminated!\n\n");

    /*建立子執行緒2,並將主執行緒ID傳遞給子執行緒2*/
    pthread_t t2;
    if (pthread_create(&t2, NULL, thread_routine2, (void *)pthread_self())!=0) {
        fprintf(stderr, "create thread fail.\n");
        exit(-1);
    }

    fprintf(stdout, "main thread: sleeping...\n");
    sleep(3);
    /*主執行緒使用pthread_exit函式終止,程序繼續存在*/
    fprintf(stdout, "main thread: exit!\n");
    pthread_exit(NULL);    

    fprintf(stdout, "main thread: never reach here!\n");
    return 0;
}

最終的執行結果如下:

thread1: hello world!
main thread: thread1 terminated!

main thread: sleeping...
thread2: I'm running...
thread2: main thread id is not equal thread2
main thread: exit!
thread2: main thread exit!
thread2: exit!
thread2: process exit!

參考資料: