1. 程式人生 > >使用POSIX Threads進行多執行緒程式設計(一)——pthread基本知識

使用POSIX Threads進行多執行緒程式設計(一)——pthread基本知識

使用POSIX Threads進行多執行緒程式設計(一)

——pthread基本知識

說明:

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

寫在開始

這份綜述是為了使你熟悉使用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標誌,以使得原始碼能夠編譯。