使用POSIX Threads進行多線程編程(一)——pthread基本知識

使用POSIX Threads進行多線程編程(一)

——pthread基本知識

說明:

  1. 本文是翻譯自《MultiThreaded-Programming-With-POSIX》,作者Guy Kerens。
  2. 本文預計翻譯三章,主要涉及pthread基本知識互斥量(鎖)條件變量,一是因爲這已經能夠引導讀者入門,二是因爲本人在工作之餘翻譯,實在時間捉急。
  3. 翻譯:張小川,轉載請保留原作者

寫在開始

這份綜述是爲了使你熟悉使用POSIX進行多線程編程,並向你展示它的特性是如何運用到“實際”編程中的。它解釋了由庫定義的不同的工具,展示瞭如何去使用他們,並且給出了用他們解決編程問題的例子。本文假定讀者對並行編程概念有一些基本的理論知識,沒有這些知識背景的讀者可能會覺得這些概念比較難理解。我會準備一個單獨的文件來爲這些只熟悉‘串行’編程的讀者來解釋這些理論背景知識。

我會假定熟悉異步編程模型(如使用在窗口環境中的(X, Motif))的讀者,理解多線程編程的概念會更容易一些。

當說起POSIX線程時,一個不可避免的問題是“應該使用哪個版本的POSIX線程標準呢?”因爲這個線程標準在幾年間已經被修改過了,你會發現不同版本標準的實現有不同的函數集,不同的默認值,不同的差別。因爲這個綜述是在一個linux系統且內核LinuxThreads library 版本v0.5上寫的,使用其他系統或者其他版本的pthread庫的程序員,應該參考相應的系統的manuals以防不兼容。並且,由於一些例子使用了系統函數,他們在用戶級的thread庫上不能工作(參考我們的parallel programming theory tutorial以得到更多信息)。說到這,我會嘗試在其他系統上檢查這些例子(Solaris 2.5浮現在腦海),以讓他們更加“跨平臺”。


什麼是線程?爲什麼使用線程

一個線程是“半個進程”,它有自己的棧,執行一段給定的代碼。與進程不同的是,線程之間的內存通常是共享的(而不同的進程通常有不同的存儲區域)。一個線程組是在同一個進程內執行的線程集合。它們共享內存,因此可以訪問相同的局變量,相同的堆存儲,相同的文件描述符等。所有這些線程並行執行(i.e.使用時間片,或者在多核處理器上真正達到並行)。

線程組相對於串行程序的優勢是:幾個操作可能並行執行,那麼事件在到達時就可以被馬上處理(例如,如果用一個線程處理用戶接口,另一個線程處理數據庫訪問,那麼就可以在執行用戶大量查詢的同時仍然響應用戶的輸入)。

線程組相對於進程組的優勢是:線程間的上下文切換要比進程間的上下文切換快得多(上下文切換指的是系統從一個正在執行的線程或進程切換到另外一個線程或進程)。而且,線程間通信通常比進程間的通信更快且更易於實現。

另一方面,由於一個線程組使用相同的存儲空間,如果一個線程導致內存的數據崩潰,其他線程也會受到影響。但是進程中,操作系統一般會保護過程彼此分離,因此如果一個進程使其存儲空間崩潰,其他的進程不受影響。使用進程的另一個好處是它可以在不同的機器上跑,而線程(一般)都只在一個機器上跑。


創建和銷燬線程

當一個多線程程序開始執行的時候,它有一個線程在跑,就是執行main()函數的線程。這已經是一個完整的線程,有它自己的thread ID。創建一個新的線程,應該調用pthread_create()函數。下面給出了使用它的例子:

/*
 * pthread_create.c
 */
#include<stdio.h>
#include<pthread.h>

void *do_loop(void *data)
{
    int i;
    int j;
    int me = *((int*)data);

    for(i = 0; i < 10; i ++)
    {
        for(j = 0; j < 5000000; j ++)
        {
            ;//just for delay
        }
        printf("'%d' - got '%d'\n", me, i );
    }

    /*terminate the thread*/
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    int thread_id;
    pthread_t p_thrd;
    int a = 1;
    int b = 2;

    thread_id = pthread_create(&p_thrd, NULL, do_loop, (void *)&a);

    //run the fun in the main thread
    do_loop((void *)&b);

    return 0;
}

關於該程序需要知道的幾點:

  1. 注意main函數也是一個線程,所以它與它所創建的線程一起執行do_loop()函數;
  2. pthread_create()函數需要四個參數。第一個參數由函數 pthread_create()使用來提供該線程的信息(即線程標識符)。第二個參數用來指定新線程的屬性。在我們的例子中我們傳遞一個NULL指針給pthread_create(),以使用默認屬性。第三個參數是該線程執行程序的名稱。第四個參數是傳遞給該函數(該線程要執行的函數)的參數。注意映射到void*並非由ANSI-C語法的要求,但是放在這兒更加清晰。
  3. 函數中的循環延遲只爲了演示線程是並行執行的。如果你的CPU跑的快使用一個大的延遲;並且你會在另一個線程之前看到一個線程的所有打印。
  4. pthread_exit()函數的調用,使得線程退出並且會釋放所有該線程佔用的資源。在一個線程的最外層函數的結束並不需要調用這個函數,因爲當其返回(return)時,該線程會自動地結束(exit)。這個函數在我們想要在一個線程中間結束它時用。

爲了使用gcc來編譯一個多線程程序,我們需要鏈接到pthread 庫。假設你已經將此庫安裝在了你的系統,如下給出瞭如何編譯我們的第一個程序

gcc pthread_create.c -o pthread_create -lpthread

注意在這個綜述中接下來的程序中,可能會需要在該編譯行中加入-D_GNU_SOURCE標誌,以使得源代碼能夠編譯。


參考:《MultiThreaded-Programming-With-POSIX》

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