1. 程式人生 > >Pthreads 多執行緒入門教程

Pthreads 多執行緒入門教程

Pthreads 是 IEEE(電子和電氣工程師協會)委員會開發的一組執行緒介面,負責指定行動式作業系統介面(POSIX). Pthreads 中的 P 表示 POSIX,實際上,Pthreads 有時候也代表 POSIX 執行緒.

前言

POSIX 委員會定義了一系列基本功能和資料結構,希望能夠被大量廠商採用,以便執行緒程式碼能夠輕鬆地在各種作業系統上移植. 委員會的夢想由 UNIX 廠商實現了,他們都廣泛 Pthreads . 最著名的例外就是 Sun,它繼續採用 Solaris 執行緒作為其主要執行緒 API .

Pthreads 指定 API 來處理執行緒要求的大部分行為. 這些行為包括建立和終止執行緒、等待執行緒完成、以及管理執行緒之間的互動. 後面的目錄中存在各種鎖定機制,能夠阻止兩個執行緒同時嘗試修改相同的資料值,這些鎖定機制包括互斥鎖、條件變數.

在 Linux 環境下,可以在 Shell 中通過 man 查詢到 Pthreads 的部分函式命令,如: man pthread_create

執行緒的建立和管理

每個執行緒都有一個在程序中唯一的執行緒識別符號,用一個數據型別 pthread_t 表示,該資料型別在 Linux 中就是一個無符號長整型資料.

建立新的執行緒

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

若建立成功,返回0;若出錯,則返回錯誤編號. thread 是執行緒識別符號,但這個引數不是由使用者指定的,而是由 pthread_create 函式在建立時將新的執行緒的識別符號放到這個變數中. attr 指定執行緒的屬性,可以用 NULL 表示預設屬性. start_routine 指定執行緒開始執行的函式,arg 是 start_routine 所需要的引數,是一個無型別指標.

預設地,執行緒在被建立時要被賦予一定的屬性,這個屬性存放在資料型別 pthread_attr_t 中,它包含了執行緒的排程策略,堆疊的相關資訊,join or detach 的狀態等.

pthread_attr_init 和 pthread_attr_destroy 函式分別用來建立和銷燬 pthread_attr_t,具體函式宣告可參考man幫助.

結束執行緒

當發生以下情形之一時,執行緒就會結束:

  • 執行緒執行的函式return了,也就是執行緒的任務已經完成;

  • 執行緒呼叫了 pthread_exit 函式;

  • 其他執行緒呼叫 pthread_cancel 結束這個執行緒;

  • 程序呼叫 exec() or exit(),結束了;

  • main() 函式先結束了,而且 main() 自己沒有呼叫 pthread_exit 來等所有執行緒完成任務.

當然,一個執行緒結束,並不意味著它的所有資訊都已經消失,後面會看到殭屍執行緒的問題.

下面介紹這兩個函式.

void pthread_exit (void *retval)

retval 是由使用者指定的引數, pthread_exit 完成之後可以通過這個引數獲得執行緒的退出狀態.

int pthread_cancel (pthread_t thread)

一個執行緒可以通過呼叫 pthread_cancel 函式來請求取消同一程序中的執行緒,這個執行緒由thread 引數指定. 如果操作成功則返回0,失敗則返回對應的錯誤編號.

看一個例子:

/******************************************************************************
 * FILE: hello.c
 * DESCRIPTION:
 * A "hello world" Pthreads program.  Demonstrates thread creation and termination.
 * AUTHOR: Blaise Barney
 * LAST REVISED: 08/09/11
 ******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5

void *PrintHello(void *threadid) {
    long tid;
    tid = (long) threadid;
    printf("Hello World! It's me, thread #%ld!\n", tid);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    pthread_t threads[NUM_THREADS];
    int rc;
    long t;
    for (t = 0; t < NUM_THREADS; t++) {
        printf("In main: creating thread %ld\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *) t);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    /* Last thing that main() should do */
    pthread_exit(NULL);
}

在 Shell 中輸入以下命令編譯執行:

gcc -Wall hello.c -lpthread -o hello
./hello

輸出結果:

In main: creating thread 0
In main: creating thread 1
In main: creating thread 2
Hello World! It's me, thread #0!
Hello World! It's me, thread #1!
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #2!
Hello World! It's me, thread #3!
Hello World! It's me, thread #4!

注意輸出的順序可能不同, 要特別注意的是, main() 顯示地呼叫了 pthread_exit() 來等待其他執行緒的結束.

下面這個例子傳給執行緒一些初始化用的引數.

/******************************************************************************
 * FILE: hello_arg2.c
 * DESCRIPTION:
 *   A "hello world" Pthreads program which demonstrates another safe way
 *   to pass arguments to threads during thread creation.  In this case,
 *   a structure is used to pass multiple arguments.
 * AUTHOR: Blaise Barney
 * LAST REVISED: 01/29/09
 ******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NUM_THREADS 8

char *messages[NUM_THREADS];

struct thread_data {
    int thread_id;
    int sum;
    char *message;
};

struct thread_data thread_data_array[NUM_THREADS];

void *PrintHello(void *threadarg) {
    int taskid, sum;
    char *hello_msg;
    struct thread_data *my_data;

    sleep(1);
    my_data = (struct thread_data *) threadarg;
    taskid = my_data->thread_id;
    sum = my_data->sum;
    hello_msg = my_data->message;
    printf("Thread %d: %s  Sum=%d\n", taskid, hello_msg, sum);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    pthread_t threads[NUM_THREADS];
    int *taskids[NUM_THREADS];
    int rc, t, sum;

    sum = 0;
    messages[0] = "English: Hello World!";
    messages[1] = "French: Bonjour, le monde!";
    messages[2] = "Spanish: Hola al mundo";
    messages[3] = "Klingon: Nuq neH!";
    messages[4] = "German: Guten Tag, Welt!";
    messages[5] = "Russian: Zdravstvytye, mir!";
    messages[6] = "Japan: Sekai e konnichiwa!";
    messages[7] = "Latin: Orbis, te saluto!";

    for (t = 0; t < NUM_THREADS; t++) {
        sum = sum + t;
        thread_data_array[t].thread_id = t;
        thread_data_array[t].sum = sum;
        thread_data_array[t].message = messages[t];
        printf("Creating thread %d\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello,
                (void *) &thread_data_array[t]);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

輸出結果:

Creating thread 0
Creating thread 1
Creating thread 2
Creating thread 3
Creating thread 4
Creating thread 5
Creating thread 6
Creating thread 7
Thread 5: Russian: Zdravstvytye, mir!  Sum=15
Thread 0: English: Hello World!  Sum=0
Thread 6: Japan: Sekai e konnichiwa!  Sum=21
Thread 3: Klingon: Nuq neH!  Sum=6
Thread 4: German: Guten Tag, Welt!  Sum=10
Thread 1: French: Bonjour, le monde!  Sum=1
Thread 7: Latin: Orbis, te saluto!  Sum=28
Thread 2: Spanish: Hola al mundo  Sum=3

對執行緒的阻塞(Joining and Detaching Threads)

阻塞是執行緒之間同步的一種方法.

int pthread_join(pthread_t threadid, void **value_ptr)

pthread_join 函式會讓呼叫它的執行緒等待 threadid 執行緒執行結束之後再執行. value_ptr 存放了其他執行緒的返回值. 一個可以被join的執行緒,僅僅可以被別的一個執行緒 join,如果同時有多個執行緒嘗試 join 同一個執行緒時,最終結果是未知的. 另外,執行緒不能 join 自己.

上面提到過,建立一個執行緒時,要賦予它一定的屬性,這其中就包括joinable or detachable 的屬性,只有被宣告成joinable的執行緒,可以被其他執行緒join. POSIX標準的最終版本指出執行緒應該被設定成joinable的. 顯式地設定一個執行緒為joinable,需要以下四個步驟:

  • Declare a pthread attribute variable of the pthread_attr_t data type

  • Initialize the attribute variable with pthread_attr_init()

  • Set the attribute detached status with pthread_attr_setdetachstate()

  • When done, free library resources used by the attribute with pthread_attr_destroy()

值得注意的的是:殭屍執行緒 ( “zombie” thread) 是一種已經退出了的 joinable 的執行緒,但是等待其他執行緒呼叫 pthread_join 來 join 它,以收集它的退出資訊(exit status). 如果沒有其他執行緒呼叫 pthread_join 來 join 它的話,它佔用的一些系統資源不會被釋放,比如堆疊. 如果main()函式需要長時間執行,並且建立大量 joinable 的執行緒,就有可能出現堆疊不足的 error . 對於那些不需要 join 的執行緒,最好利用 pthread_detach,這樣它執行結束後,資源就會及時得到釋放. 注意一個執行緒被使用 pthread_detach 之後,它就不能再被改成 joinable 的了.

總而言之,建立的每一個執行緒都應該使用 pthread_join 或者 pthread_detach 其中一個,以防止殭屍執行緒的出現.

相關函式:

pthread_detach (threadid)
pthread_attr_setdetachstate (attr,detachstate)
pthread_attr_getdetachstate (attr,detachstate)

下面看一個例子,可以具體體會顯式地設定一個執行緒為 joinable 的過程.

#include<stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <math.h>
#define NUM_THREADS 4
void *BusyWork(void *t) {
    int i;
    long tid;
    double result = 0.0;
    tid = (long) t;
    printf("Thread %ld starting...\n", tid);
    for (i = 0; i < 1000000; i++) {
        result = result + sin(i) * tan(i);
    }
    printf("Thread %ld done. Result = %e\n", tid, result);
    pthread_exit((void*) t);
}

int main(int argc, char *argv[]) {
    pthread_t thread[NUM_THREADS];
    pthread_attr_t attr;
    int rc;
    long t;
    void *status;
    /* Initialize and set thread detached attribute */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    for (t = 0; t < NUM_THREADS; t++) {
        printf("Main: creating thread %ld\n", t);
        rc = pthread_create(&thread[t], &attr, BusyWork, (void *) t);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    /* Free attribute and wait for the other threads */
    pthread_attr_destroy(&attr);

    for (t = 0; t < NUM_THREADS; t++) {
        rc = pthread_join(thread[t], &status);
        if (rc) {
            printf("ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
        printf("Main: completed join with thread %ld having a status of %ld\n",
                t, (long) status);
    }
    printf("Main: program completed. Exiting.\n");

    pthread_exit(NULL);
}

輸出結果:

Main: creating thread 0
Main: creating thread 1
Thread 0 starting...
Main: creating thread 2
Thread 1 starting...
Main: creating thread 3
Thread 2 starting...
Thread 3 starting...
Thread 1 done. Result = -3.153838e+06
Thread 2 done. Result = -3.153838e+06
Thread 0 done. Result = -3.153838e+06
Main: completed join with thread 0 having a status of 0
Main: completed join with thread 1 having a status of 1
Main: completed join with thread 2 having a status of 2
Thread 3 done. Result = -3.153838e+06
Main: completed join with thread 3 having a status of 3
Main: program completed. Exiting.

堆疊管理 (Stack Management)

pthread_attr_getstacksize (attr, stacksize)
pthread_attr_setstacksize (attr, stacksize)
pthread_attr_getstackaddr (attr, stackaddr)
pthread_attr_setstackaddr (attr, stackaddr)

POSIX標準沒有規定一個執行緒的堆疊大小. 安全可移植的程式不會依賴於具體實現預設的堆疊限制,而是顯式地呼叫 pthread_attr_setstacksize 來分配足夠的堆疊空間.

下面是關於堆疊大小的一個例子.

#include<stdlib.h>
#include <pthread.h>
#include <stdio.h>
#define NTHREADS 4
#define N 1000
#define MEGEXTRA 1000000

pthread_attr_t attr;

void *dowork(void *threadid) {
    double A[N][N];
    int i, j;
    long tid;
    size_t mystacksize;

    tid = (long) threadid;
    pthread_attr_getstacksize(&attr, &mystacksize);
    printf("Thread %ld: stack size = %li bytes \n", tid, mystacksize);
    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++)
            A[i][j] = ((i * j) / 3.452) + (N - i);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    pthread_t threads[NTHREADS];
    size_t stacksize;
    int rc;
    long t;

    pthread_attr_init(&attr);
    pthread_attr_getstacksize(&attr, &stacksize);
    printf("Default stack size = %li\n", stacksize);
    stacksize = sizeof(double) * N * N + MEGEXTRA;
    printf("Amount of stack needed per thread = %li\n", stacksize);
    pthread_attr_setstacksize(&attr, stacksize);
    printf("Creating threads with stack size = %li bytes\n", stacksize);
    for (t = 0; t < NTHREADS; t++) {
        rc = pthread_create(&threads[t], &attr, dowork, (void *) t);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    printf("Created %ld threads.\n", t);
    pthread_exit(NULL);
}

輸出結果:

Default stack size = 8388608
Amount of stack needed per thread = 9000000
Creating threads with stack size = 9000000 bytes
Thread 0: stack size = 9000000 bytes 
Created 4 threads.
Thread 1: stack size = 9000000 bytes 
Thread 2: stack size = 9000000 bytes 
Thread 3: stack size = 9000000 bytes 

其他重要函式

pthread_self ()
pthread_equal (thread1,thread2)

呼叫pthread_self 可以返回它的 thread ID. pthread_equal 比較兩個執行緒的 ID,如果不同則返回0,否則返回一個非零值.

互斥鎖 Mutex

Mutex 常常被用來保護那些可以被多個執行緒訪問的共享資源,比如可以防止多個執行緒同時更新同一個資料時出現混亂.

使用互斥鎖的一般步驟是:

  • 建立一個互斥鎖,即宣告一個pthread_mutex_t型別的資料,然後初始化,只有初始化之後才能使用;

  • 多個執行緒嘗試鎖定這個互斥鎖;

  • 只有一個成功鎖定互斥鎖,成為互斥鎖的擁有者,然後進行一些指令;

  • 擁有者解鎖互斥鎖;

  • 其他執行緒嘗試鎖定這個互斥鎖,重複上面的過程;

  • 最後互斥鎖被顯式地呼叫 pthread_mutex_destroy 來進行銷燬.

有兩種方式初始化一個互斥鎖:第一種,利用已經定義的常量初始化,例如

pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;

第二種方式是呼叫 pthread_mutex_init (mutex,attr) 進行初始化.

當多個執行緒同時去鎖定同一個互斥鎖時,失敗的那些執行緒,如果是用 pthread_mutex_lock 函式,那麼會被阻塞,直到這個互斥鎖被解鎖,它們再繼續競爭;如果是用 pthread_mutex_trylock 函式,那麼失敗者只會返回一個錯誤.

最後需要指出的是,保護共享資料是程式設計師的責任. 程式設計師要負責所有可以訪問該資料的執行緒都使用mutex這種機制,否則,不使用 mutex 的執行緒還是有可能對資料造成破壞.

相關函式 (具體宣告可以用 man 檢視 )

pthread_mutex_init (mutex,attr)
pthread_mutex_destroy (pthread_mutex_t *mutex)
pthread_mutexattr_init (attr)
pthread_mutexattr_destroy (attr)
phtread_mutex_lock(pthread_mutex_t *mutex)
phtread_mutex_trylock(pthread_mutex_t *mutex)
phtread_mutex_unlock(pthread_mutex_t *mutex)

下面是一個利用多執行緒進行向量點乘的程式,其中需要對 dotstr.sum 這個共同讀寫的資料進行保護.

/*****************************************************************************
 * FILE: dotprod_mutex.c
 * DESCRIPTION:
 *   This example program illustrates the use of mutex variables
 *   in a threads program. The main data is made available to all threads through
 *   a globally accessible  structure. Each thread works on a different
 *   part of the data. The main thread waits for all the threads to complete
 *   their computations, and then it prints the resulting sum.
 * SOURCE: Vijay Sonnad, IBM
 * LAST REVISED: 01/29/09 Blaise Barney
 ******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

/*
 The following structure contains the necessary information
 to allow the function "dotprod" to access its input data and
 place its output into the structure.  This structure is
 unchanged from the sequential version.
 */

typedef struct {
    double *a;
    double *b;
    double sum;
    int veclen;
} DOTDATA;

/* Define globally accessible variables and a mutex */

#define NUMTHRDS 4
#define VECLEN 100000
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;

/*
 The function dotprod is activated when the thread is created.
 As before, all input to this routine is obtained from a structure
 of type DOTDATA and all output from this function is written into
 this structure. The benefit of this approach is apparent for the
 multi-threaded program: when a thread is created we pass a single
 argument to the activated function - typically this argument
 is a thread number. All  the other information required by the
 function is accessed from the globally accessible structure.
 */

void *dotprod(void *arg) {

    /* Define and use local variables for convenience */

    int i, start, end, len;
    long offset;
    double mysum, *x, *y;
    offset = (long) arg;

    len = dotstr.veclen;
    start = offset * len;
    end = start + len;
    x = dotstr.a;
    y = dotstr.b;

    /*
     Perform the dot product and assign result
     to the appropriate variable in the structure.
     */
    mysum = 0;
    for (i = start; i < end; i++) {
        mysum += (x[i] * y[i]);
    }

    /*
     Lock a mutex prior to updating the value in the shared
     structure, and unlock it upon updating.
     */
    pthread_mutex_lock(&mutexsum);
    dotstr.sum += mysum;
    printf("Thread %ld did %d to %d: mysum=%f global sum=%f\n", offset, start,
            end, mysum, dotstr.sum);
    pthread_mutex_unlock(&mutexsum);
    pthread_exit((void*) 0);
}

/*
 The main program creates threads which do all the work and then print out result
 upon completion. Before creating the threads, The input data is created. Since
 all threads update a shared structure, we need a mutex for mutual exclusion.
 The main thread needs to wait for all threads to complete, it waits for each one
 of the threads. We specify a thread attribute value that allow the main thread to
 join with the threads it creates. Note also that we free up handles  when they
 are no longer needed.
 */

int main(int argc, char *argv[]) {
    long i;
    double *a, *b;
    void *status;
    pthread_attr_t attr;

    /* Assign storage and initialize values */

    a = (double*) malloc(NUMTHRDS * VECLEN * sizeof(double));
    b = (double*) malloc(NUMTHRDS * VECLEN * sizeof(double));

    for (i = 0; i < VECLEN * NUMTHRDS; i++) {
        a[i] = 1;
        b[i] = a[i];
    }

    dotstr.veclen = VECLEN;
    dotstr.a = a;
    dotstr.b = b;
    dotstr.sum = 0;

    pthread_mutex_init(&mutexsum, NULL);

    /* Create threads to perform the dotproduct  */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (i = 0; i < NUMTHRDS; i++) {
        /* Each thread works on a different set of data.
         * The offset is specified by 'i'. The size of
         * the data for each thread is indicated by VECLEN.
         */
        pthread_create(&callThd[i], &attr, dotprod, (void *) i);
    }

    pthread_attr_destroy(&attr);
    /* Wait on the other threads */

    for (i = 0; i < NUMTHRDS; i++) {
        pthread_join(callThd[i], &status);
    }
    /* After joining, print out the results and cleanup */

    printf("Sum =  %f \n", dotstr.sum);
    free(a);
    free(b);
    pthread_mutex_destroy(&mutexsum);
    pthread_exit(NULL);
}

輸出結果:

Thread 3 did 300000 to 400000: mysum=100000.000000 global sum=100000.000000
Thread 0 did 0 to 100000: mysum=100000.000000 global sum=200000.000000
Thread 2 did 200000 to 300000: mysum=100000.000000 global sum=300000.000000
Thread 1 did 100000 to 200000: mysum=100000.000000 global sum=400000.000000
Sum =  400000.000000 

條件變數 Condition Variable

互斥鎖只有兩種狀態,這限制了它的用途. 條件變數允許執行緒在阻塞的時候等待另一個執行緒傳送的訊號,當收到訊號後,阻塞的執行緒就被喚醒並試圖鎖定與之相關的互斥鎖. 條件變數要和互斥鎖結合使用.

條件變數的宣告和初始化

通過宣告 pthread_cond_t 型別的資料,並且必須先初始化才能使用.

初始化的方法也有兩種:

第一種,利用內部定義的常量,例如:

pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;

第二種,利用函式 pthread_cond_init(cond,attr),其中 attr 由 pthread_condattr_init() 和 pthread_condattr_destroy() 建立和銷燬.

可以用 pthread_cond_destroy() 銷燬一個條件變數.

相關函式:

pthread_cond_wait (condition,mutex)
pthread_cond_signal (condition)
pthread_cond_broadcast (condition)

pthread_cond_wait() 會阻塞呼叫它的執行緒,直到收到某一訊號. 這個函式需要在 mutex 已經被鎖之後進行呼叫,並且當執行緒被阻塞時,會自動解鎖 mutex. 訊號收到後,執行緒被喚醒,這時 mutex 又會被這個執行緒鎖定.

pthread_cond_signal() 函式結束時,必須解鎖 mutex,以供 pthread_cond_wait() 鎖定mutex. 當不止一個執行緒在等待訊號時,要用 pthread_cond_broadcast() 代替 pthread_cond_signal() 來告訴所有被該條件變數阻塞的執行緒結束阻塞狀態.

下面是一個例子,三個執行緒共同訪問 count 變數,thread 2 和 thread 3 競爭地對其進行加 1 的操作,thread 1 等 count 達到 12 的時候,一次性加 125 . 然後 thread 2 和 thread 3 再去競爭 count 的控制權,直到完成自己的對 count 加 10 次的任務.

/******************************************************************************
 * FILE: condvar.c
 * DESCRIPTION:
 *   Example code for using Pthreads condition variables.  The main thread
 *   creates three threads.  Two of those threads increment a "count" variable,
 *   while the third thread watches the value of "count".  When "count"
 *   reaches a predefined limit, the waiting thread is signaled by one of the
 *   incrementing threads. The waiting thread "awakens" and then modifies
 *   count. The program continues until the incrementing threads reach
 *   TCOUNT. The main program prints the final value of count.
 * SOURCE: Adapted from example code in "Pthreads Programming", B. Nichols
 *   et al. O'Reilly and Associates.
 * LAST REVISED: 10/14/10  Blaise Barney
 ******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NUM_THREADS  3
#define TCOUNT 10
#define COUNT_LIMIT 12

int count = 0;
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;

void *inc_count(void *t) {
    int i;
    long my_id = (long) t;

    for (i = 0; i < TCOUNT; i++) {
        pthread_mutex_lock(&count_mutex);
        count++;

        /*
         Check the value of count and signal waiting thread when condition is
         reached.  Note that this occurs while mutex is locked.
         */
        if (count == COUNT_LIMIT) {
            printf("inc_count(): thread %ld, count = %d  Threshold reached. ",
                    my_id, count);
            pthread_cond_signal(&count_threshold_cv);
            printf("Just sent signal.\n");
        }
        printf("inc_count(): thread %ld, count = %d, unlocking mutex\n", my_id,
                count);
        pthread_mutex_unlock(&count_mutex);

        /* Do some work so threads can alternate on mutex lock */
        sleep(1);
    }
    pthread_exit(NULL);
}

void *watch_count(void *t) {
    long my_id = (long) t;

    printf("Starting watch_count(): thread %ld\n", my_id);

    /*
     Lock mutex and wait for signal.  Note that the pthread_cond_wait routine
     will automatically and atomically unlock mutex while it waits.
     Also, note that if COUNT_LIMIT is reached before this routine is run by
     the waiting thread, the loop will be skipped to prevent pthread_cond_wait
     from never returning.
     */
    pthread_mutex_lock(&count_mutex);
    while (count < COUNT_LIMIT) {
        printf("watch_count(): thread %ld Count= %d. Going into wait...\n",
                my_id, count);
        pthread_cond_wait(&count_threshold_cv, &count_mutex);
        printf(
                "watch_count(): thread %ld Condition signal received. Count= %d\n",
                my_id, count);
        printf("watch_count(): thread %ld Updating the value of count...\n",
                my_id, count);
        count += 125;
        printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
    }
    printf("watch_count(): thread %ld Unlocking mutex.\n", my_id);
    pthread_mutex_unlock(&count_mutex);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    int i, rc;
    long t1 = 1, t2 = 2, t3 = 3;
    pthread_t threads[3];
    pthread_attr_t attr;

    /* Initialize mutex and condition variable objects */
    pthread_mutex_init(&count_mutex, NULL);
    pthread_cond_init(&count_threshold_cv, NULL);

    /* For portability, explicitly create threads in a joinable state */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&threads[0], &attr, watch_count, (void *) t1);
    pthread_create(&threads[1], &attr, inc_count, (void *) t2);
    pthread_create(&threads[2], &attr, inc_count, (void *) t3);

    /* Wait for all threads to complete */
    for (i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    printf(
            "Main(): Waited and joined with %d threads. Final value of count = %d. Done.\n",
            NUM_THREADS, count);

    /* Clean up and exit */
    pthread_attr_destroy(&attr);
    pthread_mutex_destroy(&count_mutex);
    pthread_cond_destroy(&count_threshold_cv);
    pthread_exit(NULL);
}

輸出結果:

inc_count(): thread 2, count = 1, unlocking mutex
Starting watch_count(): thread 1
watch_count(): thread 1 Count= 1. Going into wait...
inc_count(): thread 3, count = 2, unlocking mutex
inc_count(): thread 2, count = 3, unlocking mutex
inc_count(): thread 3, count = 4, unlocking mutex
inc_count(): thread 2, count = 5, unlocking mutex
inc_count(): thread 3, count = 6, unlocking mutex
inc_count(): thread 2, count = 7, unlocking mutex
inc_count(): thread 3, count = 8, unlocking mutex
inc_count(): thread 2, count = 9, unlocking mutex
inc_count(): thread 3, count = 10, unlocking mutex
inc_count(): thread 2, count = 11, unlocking mutex
inc_count(): thread 3, count = 12  Threshold reached. Just sent signal.
inc_count(): thread 3, count = 12, unlocking mutex
watch_count(): thread 1 Condition signal received. Count= 12
watch_count(): thread 1 Updating the value of count...
watch_count(): thread 1 count now = 137.
watch_count(): thread 1 Unlocking mutex.
inc_count(): thread 2, count = 138, unlocking mutex
inc_count(): thread 3, count = 139, unlocking mutex
inc_count(): thread 2, count = 140, unlocking mutex
inc_count(): thread 3, count = 141, unlocking mutex
inc_count(): thread 2, count = 142, unlocking mutex
inc_count(): thread 3, count = 143, unlocking mutex
inc_count(): thread 2, count = 144, unlocking mutex
inc_count(): thread 3, count = 145, unlocking mutex
Main(): Waited and joined with 3 threads. Final value of count = 145. Done.