歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux多線程編程詳解 [By: HarryAlex]

Linux多線程編程詳解 [By: HarryAlex]

日期:2017/3/1 12:13:52   编辑:關於Linux

本文內容主要參考於《Linux程序設計·第3版》、《Linux環境C程序設計》、《C語言核心技術》、《深入理解計算機系統·第2版》,代碼運行環境:
Linux version 3.10.0-123.el7.x86_64 (root@bsp4) (gcc version 4.8.2 20140120 (Red Hat 4.8.2-16) (GCC) ) #1 SMP Thu Jun 4 17:17:49 CST 2015

1. Linux進程與線程

Linux進程創建一個新線程時,線程將擁有自己的棧(因為線程有自己的局部變量),但與它的創建者共享全局變量、文件描述符、信號句柄和當前目錄狀態。

Linux通過fork創建子進程與創建線程之間是有區別的:fork創建出該進程的一份拷貝,這個新進程擁有自己的變量和自己的PID,它的時間調度是獨立的,它的執行幾乎完全獨立於父進程。

進程可以看成一個資源的基本單位,而線程是程序調度的基本單位,一個進程內部的線程之間共享進程獲得的時間片。

2. _REENTRANT宏

在一個多線程程序中,默認情況下只有一個errno變量供所有的線程共享。在一個線程准備獲取剛才的錯誤代碼時,errno變量很容易被另一個線程中的函數調用所改變。類似的問題還存在於fputs之類的函數中,這些函數通常用一個單獨的全局性緩沖區域來緩存輸出數據。

為解決這個問題,需要使用可重入的例程。可重入代碼可以被多次調用而仍然工作正常。編寫的多線程程序,通過定義宏_REENTRANT來告訴編譯器我們需要可重入功能,這個宏的定義必須出現於程序中的任何#include語句之前。

_REENTRANT為我們做三件事情,並且做得非常優雅:
(1)對部分函數重新定義它們的可安全重入的版本,這些函數名字一般不會發生改變,只是會在函數名後面添加_r字符串,如函數名foo變成foo_r;
(2)stdio.h中原來以宏的形式實現的一些函數將變成可安全重入函數;
(3)在error.h中定義的變量error現在將成為一個函數調用,它能夠以一種安全的多線程方式來獲取真正的errno的值。

3. 線程的基本函數

大多數pthread_xxx函數在失敗時,並未遵循UNIX函數的慣例返回-1,這種情況在UNIX函數中屬於少數。如果調用成功,則返回值是0,如果失敗則返回錯誤代碼。

3.1 線程創建

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

參數說明:
thread:指向pthread_t類型的指針,用於引用新創建的線程。下面是pthread_t類型定義:
typedef unsigned long int pthread_t; //come from /usr/include/bits/pthreadtypes.h
attr:用於設置線程的屬性,一般不需要特殊的屬性,所以可以簡單地設置為NULL。
start_routine:傳遞新線程所要執行的函數地址,該函數入參是(void *),返回類型也是(void *)。
arg:新線程所要執行的函數的參數。
調用如果成功,則返回值是0,如果失敗則返回錯誤代碼。

3.2 線程終止

#include
void pthread_exit(void *retval);

參數說明:
retval:返回指針,指向線程向要返回的某個對象。線程通過調用pthread_exit函數終止執行,並返回一個指向某對象的指針。注意:絕不能用它返回一個指向局部變量的指針,因為線程調用該函數後,這個局部變量就不存在了。

3.3 線程回收

#include
int pthread_join(pthread_t th, void **thread_return);

pthread_join()函數,以阻塞的方式等待th指定的線程結束。當函數返回時,被等待線程的資源被收回。如果線程已經結束,那麼該函數會立即返回。並且th指定的線程必須是joinable的。

參數說明:
th:將要等待的線程,線程通過pthread_create返回的標識符來指定。
thread_return:一個指針,指向另一個指針,而後者指向線程的返回值。

一個簡單的多線程Demo(thread1.c):
#include 
#include 
#include 
#include 

void *thread_function(void *arg);

char message[] = "Hello World!";

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;

    res = pthread_create(&a_thread, NULL, thread_function, (void *)message);
    if(res != 0) {
        perror("\nThread creation failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for thread to finish...\n");

    res = pthread_join(a_thread, &thread_result);
    if(res != 0) {
        perror("\nThread join failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("Thread joined, it returned: %s\n", (char *)thread_result);
    printf("Message is now: %s\n", message);

    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    printf("thread_function() is running. Argument is: %s\n", (char *)arg);
    sleep(3);
    strcpy(message, "Bye!");
    pthread_exit("Thank you for your CPU time!");
}

編譯這個程序時,首先需要定義宏_REENTRANT:
gcc -D _REENTRANT thread1.c -o thread1 -lpthread
運行這個程序:$ ./thread1輸出:
Waiting for thread to finish...
thread_function() is running. Argument is: Hello World!
Thread joined, it returned: Thank you for your CPU time!
Message is now: Bye!

這個例子值得我們去花時間理解,因為它將作為幾個例子的基礎。
pthread_exit(void *retval)本身返回的就是指向某個對象的指針,因此,pthread_join(pthread_t th, void **thread_return)中的thread_return是二級指針,指向線程返回值的指針。

可以看到,我們創建的新線程修改的數組message的值,而原先的線程也可以訪問該數組。如果我們調用的是fork而不是pthread_create,就不會有這樣的效果了。原因是fork創建子進程之後,子進程會拷貝父進程,兩者分離,相互不干擾,而線程之間則是共享進程的相關資源。

4. 線程並發

接下來,我們來編寫一個程序,以驗證兩個線程的執行是同時進行的。當然,如果是在一個單處理器系統上,線程的同時執行就需要靠CPU在線程之間的調度來實現了。

我們的程序需要利用一個原理:即除了局部變量外,所有其他的變量在一個進程中的所有線程之間是共享的。在這個程序中,我們是在兩個線程之間使用輪詢技術,這種方式稱為忙等待,所以它的效率會很低。在本文的後續部分,我們將介紹一種更好的解決辦法。下面的代碼中,兩個線程會不斷的輪詢判斷flag的值是否滿足各自的要求(thread2.c):
#include 
#include 
#include 

int flag = 1;
void *thread_function(void *arg);

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;
    int count = 1;
    
    res = pthread_create(&a_thread, NULL, thread_function, NULL);
    if(res != 0) {
        perror("\nThread creation failed!\n");
        exit(EXIT_FAILURE);
    }

    while(count++ <= 10) {
        if(flag == 1) {
            printf("1");
            flag = 2;
        } else {
            sleep(1);
        }
    }

    printf("\nWaiting for thread to finish...\n");
    res = pthread_join(a_thread, &thread_result);
    if(res != 0) {
        perror("\nThread join failed!\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    int count = 1;
    while(count++ <= 10) {      /* 不共享進程的局部變量count */
        if(flag == 2) {         /* 共享進程的全局變量flag */
            printf("2");
            flag = 1;
        } else {
            sleep(1);
        }
    }
}

編譯這個程序:
gcc -D _REENTRANT thread2.c -o thread2 -lpthread
運行這個程序:
$ ./thread2
1212121212
Waiting for thread to finish...

5. 線程同步

在上述示例中,我們采用輪詢的方式在兩個線程之間不停地切換是非常笨拙且效率低下的實現方式,幸運的是,專門有一些設計好的函數為我們提供更好的控制線程執行和訪問臨界區代碼的方法。

本小節將介紹兩個線程同步的基本方法:信號量和互斥量。這兩種方法很相似,事實上,它們可以互相通過對方來實現。但在實際的應用中,對於一些具體情況,可能使用信號量或互斥量中的一個更符合問題的語義,並且效果更好。

5.1 用信號量進行同步

5.1.1 信號量創建


#include
int sem_init(sem_t *sem, int pshared, unsigned int value);

參數說明:
sem:信號量對象。
pshared:控制信號量的類型,0表示這個信號量是當前進程的局部信號量,否則,這個信號量就可以在多個進程之間共享。
value:信號量的初始值。

5.1.2 信號量控制

#include
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);

sem_post的作用是以原子操作的方式給信號量的值加1,即釋放信號量。
sem_wait的作用是以原子操作的方式給信號量的值減1,但它會等到信號量非0時才會開始減法操作。如果對值為0的信號量調用sem_wait,這個函數就會等待,直到有線程增加了該信號量的值使其不再為0。

5.1.3 信號量銷毀

#include
int sem_destory(sem_t *sem);

這個函數的作用是,用完信號量後對它進行清理,清理該信號量所擁有的資源。如果你試圖清理的信號量正被一些線程等待,就會收到一個錯誤。

與大多數Linux函數一樣,這些函數在成功時都返回0。
下面編碼實現輸入字符串,統計每行的字符個數,以“end”結束輸入(thread3.c):
#include 
#include 
#include 
#include 
#include 

#define SIZE 1024
char buffer[SIZE] = {0};
void *thread_function(void *arg);
sem_t sem;

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;

    res = sem_init(&sem, 0, 0);
    if(res != 0) {
        perror("\nSem init failed!\n");
        exit(EXIT_FAILURE);
    }

    res = pthread_create(&a_thread, NULL, thread_function, NULL);
    if(res != 0) {
        perror("\nThread create failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("Input some text, Enter 'end' to finish: \n");

    while(scanf("%s", buffer)) {
        sem_post(&sem);
        if(strncmp("end", buffer, 3) == 0) {
            break;
        }
    }

    printf("Waiting for thread to finish...\n");

    res = pthread_join(a_thread, &thread_result);
    if(res != 0) {
        perror("\nThread join failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("Thread join!\n");
    sem_destroy(&sem);
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    sem_wait(&sem);
    while(strncmp("end", buffer, 3) != 0) {
        printf("You input %d characters/n", strlen(buffer));
        sem_wait(&sem);
    }
    pthread_exit(NULL);
}

編譯這個程序:
gcc -D _REENTRANT thread3.c -o thread3 -lpthread
運行這個程序:
$ ./thread3
Input some text. Enter 'end' to finish
123
You input 3 characters
1234
You input 4 characters
12345
You input 5 characters
end
Waiting for thread to finish…
Thread join!

通過使用信號量,我們阻塞了統計字符個數的線程,這個程序似乎對快速的文本輸入和悠閒的暫停都很適用,比之前的輪詢解決方案效率上有了本質的提高。

5.2 用互斥信號量進行同步

另一種用在多線程程序中同步訪問的方法是使用互斥量。它允許程序員鎖住某個對象,使得每次只能有一個線程訪問它。為了控制對關鍵代碼的訪問,必須在進入這段代碼之前鎖住一個互斥量,然後在完成操作之後解鎖它。用於互斥量的基本函數和用於信號量的函數非常相似:

#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

注:關於關鍵字restrict

概括的說,關鍵字restrict只用於限定指針;該關鍵字用於告知編譯器,所有修改該指針所指向內容的操作全部都是基於(base on)該指針的,即不存在其它對對象進行修改的途徑。這樣的後果是幫助編譯器進行更好的代碼優化,生成更有效率的匯編代碼。下面是從《C語言核心技術》一書上摘的:
“void *memcpy(void *restrict dest, const void *restrict src, sizi_t n) 這是一個很有用的內存復制函數,由於兩個參數都加了restrict限定,所以兩塊區域不能重疊,即 dest指針所指的區域,不能讓別的指針來修改,即src的指針不能修改. 相對應的另一個函數 memmove(void *dest, const void *src, size_t)則可以重疊”。

與其他函數一樣,成功時返回0,失敗時將返回錯誤代碼,但這些函數並不設置errno,所以必須對函數的返回代碼進行檢查。互斥量的屬性設置這裡不討論,因此設置成NULL。我們用互斥量來重寫剛才的代碼如下(thread4.c):

#include 
#include 
#include 
#include 
#include 

#define SIZE 1024
char buffer[SIZE] = {0};
void *thread_function(void *arg);
pthread_mutex_t mutex;

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;

    res = pthread_mutex_init(&mutex, NULL);
    if(res != 0) {
        perror("\nMutex init failed!");
        exit(EXIT_FAILURE);
    }

    res = pthread_create(&a_thread, NULL, thread_function, NULL);
    if(res != 0) {
        perror("\nThread create failed!");
        exit(EXIT_FAILURE);
    }

    printf("Input some text, Enter 'end' to finish: \n");

    while(1) {
        pthread_mutex_lock(&mutex);
        scanf("%s", buffer);
        pthread_mutex_unlock(&mutex);
        if(strncmp("end", buffer, 3) == 0) {
            break;
        }
        sleep(1);
    }

    res = pthread_join(a_thread, &thread_result);
    if(res != 0) {
        perror("\nThread join failed!");
        exit(EXIT_FAILURE);
    }

    printf("Thread joined\n");
    pthread_mutex_destroy(&mutex);
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    sleep(1);
    while(1) {
        pthread_mutex_lock(&mutex);
        printf("You input %d characters\n", strlen(buffer));
        pthread_mutex_unlock(&mutex);
        if(strncmp("end", buffer, 3) == 0) {
            break;
        }
        sleep(1);
    }
}

編譯這個程序:gcc -D _REENTRANT thread4.c -o thread4 -lpthread,可見,程序運行結果與thread3.c相同。

6. 線程的屬性

之前我們並未談及到線程的屬性,然而,可以控制的線程屬性是非常多的,這裡面只列舉一些常用的。比如在前面的示例中,我們都使用了pthread_join()同步線程,但其實有些情況下,我們並不需要。如:主線程為服務線程,而第二個線程為數據備份線程,備份工作完成之後,第二個線程可以直接終止,它沒有必要再返回到主線程中。因此,我們可以創建一個“脫離線程”。下面介紹幾個常用的函數:

(1)int pthread_attr_init(pthread_attr_t *attr);
功能:對線程屬性變量的初始化。
attr:線程屬性。
函數返回值:成功:0,失敗:-1

(2)int pthread_attr_setscope(pthread_attr_t *attr, int scope);
功能:設置線程綁定屬性。
attr:線程屬性。
scope:PTHREAD_SCOPE_SYSTEM(綁定);PTHREAD_SCOPE_PROCESS(非綁定)
函數返回值:成功:0,失敗:-1

(3)int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:設置線程分離屬性。
attr:線程屬性。
detachstate:PTHREAD_CREATE_DETACHED(分離);PTHREAD_CREATE_JOINABLE(非分離)
函數返回值:成功:0,失敗:-1

(4)int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
功能:設置創建線程的調度策略。
attr:線程屬性;
policy:線程調度策略:SCHED_FIFO、SCHED_RR和SCHED_OTHER。
函數返回值:成功:0,失敗:-1

SCHED_FIFO策略允許一個線程運行直到有更高優先級的線程准備好,或者直到它自願阻塞自己。在SCHED_FIFO調度策略下,當有一個線程准備好時,除非有平等或更高優先級的線程已經在運行,否則它會很快開始執行。SCHED_RR(輪循)策略與SCHED_FIFO策略是基本相同的,不同之處在於:如果有一個SCHED_RR策略的線程執行了超過一個固定的時期(時間片間隔)沒有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同優先級的線程准備好時,運行的線程將被搶占以便准備好的線程可以執行。當有SCHED_FIFO或SCHED_RR策賂的線程在一個條件變量上等待或等待加鎖的一個互斥量時,它們將以優先級順序被喚醒。即,如果一個低優先級的SCHED_FIFO線程和一個高優先織的SCHED_FIFO線程都在等待同一個互斥量時,當互斥量被解鎖時,高優先級線程將總是被首先解除阻塞。

(5)int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
功能:設置線程優先級。
attr:線程屬性。
param:線程優先級。
函數返回值:成功:0,失敗:-1

(6)int pthread_attr_destroy(pthread_attr_t *attr);
功能:對線程屬性變量的銷毀。
attr:線程屬性。
函數返回值:成功:0,失敗:-1

(7)其他
/* 設置新創建線程棧的保護區大小 */
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
/* 決定怎樣設置新創建線程的調度屬性 */
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
/* 兩者共同決定了線程棧的基地址以及堆棧的最小尺寸(以字節為單位) */
int pthread_attr_setstack(pthread_attr_t *attr, void *stackader, size_t stacksize);
/* 決定了新創建線程的棧的基地址 */
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackader);
/* 決定了新創建線程的棧的最小尺寸(以字節為單位) */
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

例:創建優先級為10的線程。
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); //綁定
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //分離
pthread_attr_setschedpolicy(&attr, SCHED_RR);
param.sched_priority = 10;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(xxx, &attr, xxx, xxx);
pthread_attr_destroy(&attr);

下面實現一個脫離線程的程序(thread5.c),創建一個線程,其屬性設置為脫離狀態。子線程結束時,要使用pthread_exit,原來的主線程不再等待與子線程合並。代碼如下:
#include 
#include 
#include 
#include 

void *thread_function(void *arg);

char message[] = "Hello World!";
int thread_finished = 0;

int main()
{
    int res;
    pthread_t a_thread;
    pthread_attr_t thread_attr;
    
    res = pthread_attr_init(&thread_attr);
    if(res != 0) {
        perror("\nAttribute creation failed!\n");
        exit(EXIT_FAILURE);
    }
    
    res = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);  // 分離於主線程
    if(res != 0) {
        perror("\nSetting detached attribute failed!\n");
        exit(EXIT_FAILURE);
    }
    
    res = pthread_create(&a_thread, &thread_attr, thread_function, (void *)message);
    if(res != 0) {
        perror("\nThread creation failed!\n");
        exit(EXIT_FAILURE);
    }
    
    pthread_attr_destroy(&thread_attr);
    while(!thread_finished) {
        printf("Waiting for thread to say it's finished...\n");
        sleep(1);
    }
    
    printf("Other thread finished, Bye!\n");
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    printf("thread_function() is running. Argument was %s\n", (char *)arg);
    sleep(4);
    printf("Second thread setting finished flag, and exiting now\n");
    thread_finished = 1;
    pthread_exit(NULL);
}

編譯這個程序:
gcc -D _REENTRANT thread5.c -o thread5 -lpthread
運行這個程序:
$ ./thread5
Waiting for thread to say it's finished...
thread_function() is running. Argument was Hello World!
Waiting for thread to say it's finished...
Waiting for thread to say it's finished...
Waiting for thread to say it's finished...
Second thread setting finished flag, and exiting now
Other thread finished, Bye!

可見,通過設置線程的屬性,我們還可以控制線程的調試,其方式與設置脫離狀態是一樣的。

7. 取消一個線程

有時,我們想讓一個線程要求另一個線程終止。線程有方法做到這一點,與信號處理一樣,線程可以在被要求終止時停止運行。先來看一下用於請求一個線程終止的函數:

#include
int pthread_cancel(pthread_t thread);

這個函數簡單易懂,提供一個線程標識符,我們就可以發送請求來取消該線程。一個線程可以用pthread_setcancelstate設置自己的取消狀態。

#include
int pthread_setcancelstate(int state, int *oldstate);
參數說明:
state:可以是PTHREAD_CANCEL_ENABLE表示接受其它線程的取消請求,也可以是PTHREAD_CANCEL_DISABLE表示將不接受其它線程的取消請求。
oldstate:獲取原取消狀態。如果對它沒興趣,可以簡單地設置為NULL。如果取消請求被接受,線程可以進入第二個控制層次,用pthread_setcanceltype設置取消類型:

#include
int pthread_setcanceltype(int type, int *oldtype);
參數說明:
type:可以取PTHREAD_CANCEL_ASYNCHRONOUS,它將在接收到取消請求後立即采取行動;另一個是PTHREAD_CANCEL_DEFERRED,它將在接收到取消請求後,一直等待直到線程執行了下述函數之一後才采取行動:pthread_join()、pthread_cond_wait()、pthread_cond_timedwait()、pthread_testcancel()、sem_wait()或sigwait()。
oldtype:保存先前的狀態,如果不想知道先前的狀態,可以傳遞NULL。
默認情況下,線程在啟動時的取消狀態為PTHREAD_CANCEL_ENABLE,取消類型是PTHREAD_CANCEL_DEFERRED。

下面編寫代碼(thread6.c),主線程向它創建的線程發送一個取消請求:
#include 
#include 
#include 

void *thread_function(void *arg);

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;

    res = pthread_create(&a_thread, NULL, thread_function, NULL);
    if(res != 0) {
        perror("\nThread create failed!\n");
        exit(EXIT_FAILURE);
    }

    sleep(4);
    printf("Canceling thread...\n");

    res = pthread_cancel(a_thread);
    if(res != 0) {
        perror("\nThread cancel failed!\n");
        exit(EXIT_FAILURE);
    }

    printf ("Waiting for thread to finished...\n");
    
    res = pthread_join(a_thread, &thread_result);
    if(res != 0) {
        perror ("\nThread join failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("Thread canceled!");
    exit(EXIT_FAILURE);
}

void *thread_function(void *arg)
{
    int i;
    int res;

    res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  // 可以被取消
    if(res != 0) {
        perror("\nThread setcancelstate failed!\n");
        exit(EXIT_FAILURE);
    }

    res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);  // 等待阻塞後再結束
    if(res != 0) {
        perror("\nThread setcanceltype failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("thread_function is running...\n");

    for(i = 0; i < 10; i++) {
        printf("Thread is still running (%d)...\n", i);
        sleep(1);
    }
    pthread_exit(0);
}

編譯這個程序:
gcc -D _REENTRANT thread6.c -o thread6 -lpthread
運行這個程序:
$ ./thread6
thread_function is running...
Thread is still running (0)...
Thread is still running (1)...
Thread is still running (2)...
Thread is still running (3)...
Canceling thread...
Waiting for thread to finished...

8. 多線程

之前,我們所編寫的代碼裡面都僅僅是創建了一個線程,現在我們來演示一下如何創建多線程的程序(thread7.c):
#include 
#include 
#include 

#define  NUM     6

void *thread_function(void *arg);

int main()
{
    int res;
    pthread_t a_thread[NUM];
    void *thread_result;
    int index;

    for(index = 0; index < NUM; index++) {
        res = pthread_create(&a_thread[index], NULL, thread_function, (void *)index);
        if(res != 0) {
            perror("\nThread create failed!\n");
            exit(EXIT_FAILURE);
        }
        sleep(1);
    }

    printf("Waiting for threads to finished...\n");

    for(index = NUM - 1; index >= 0; index--) {
        res = pthread_join(a_thread[index], &thread_result);
        if(0 == res) {
            printf("Picked up a thread:%d\n", index + 1);
        } else {
            perror("\npthread_join failed!\n");
        }
    }

    printf("All done\n");
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    int my_number = (int)arg;
    int rand_num;

    printf("thread_function is running. Argument was %d\n", my_number);
    rand_num = 1 + (int)(9.0 * rand()/(RAND_MAX + 1.0));
    sleep(rand_num);
    printf("Bye from %d\n", my_number);
    pthread_exit(NULL);
}

編譯這個程序:
gcc -D _REENTRANT thread7.c -o thread7 -lpthread
運行這個程序:
$ ./thread7
thread_function is running. Argument was 0
thread_function is running. Argument was 1
thread_function is running. Argument was 2
thread_function is running. Argument was 3
thread_function is running. Argument was 4
Bye from 1
thread_function is running. Argument was 5
Waiting for threads to finished...
Bye from 5
Picked up a thread:6
Bye from 0
Bye from 2
Bye from 3
Bye from 4
Picked up a thread:5
Picked up a thread:4
Picked up a thread:3
Picked up a thread:2
Picked up a thread:1
All done

9. 小結

本文主要介紹了Linux環境下的多線程編程,介紹了信號量和互斥量、線程屬性控制、線程同步、線程終止、取消線程及多線程並發。本文只作為Linux多線程編程入門之用。

Copyright © Linux教程網 All Rights Reserved