Pthread - 互斥量(Mutex) 和 條件變量(Condition variable)

Pthread - 互斥量(Mutex) 和 條件變量(Condition variable)

Mutex

Mutex 全稱 “mutual exclusion”,是一種實現線程同步的方法。
在訪問共享資源時,Mutex 的作用如同一把“鎖”。給定時間只有一個線程可以對 Mutex 加鎖,其他線程必須等待當前線程解鎖才能重新獲取 Mutex。如此線程便可以依次訪問共享資源。
通常Mutex 使用場景是多個線程訪問全局變量(又被稱作“critical section”),Mutex可以確保變量的最終值和單線程下操作結果保持一致。
Pthread 中 Mutex 的操作函數有:

pthread_mutexattr_init (attr)
thread_mutex_init (mutex,attr)
pthread_mutex_lock (mutex)
pthread_mutex_trylock (mutex)
pthread_mutex_unlock (mutex)
pthread_mutex_destroy (mutex)
pthread_mutexattr_destroy (attr)

其中pthread_mutexattr_init和pthread_mutexattr_destroy初始化和銷燬 Mutex屬性變量(pthread_mutexattr_t);pthread_mutex_init和pthread_mutex_destroy初始化和銷燬互斥量(pthread_mutex_t); pthread_mutex_lock和 pthread_mutex_trylock嘗試對 Mutex 加鎖,不同之處在於前者在Mutex 已被其它線程獲取會阻塞,而後者會立即返回一個“EBUSY”的錯誤碼。pthread_mutex_unlock對 Mutex 解鎖。

下面提供一個示例,程序實現兩向量的點積:

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

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

#define NUMTHRDS 4
#define VECLEN 100

DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;


void *dotprod(void *arg)
{
    long offset = (long)arg;
    int len = dotstr.veclen;
    int start = offset*len;
    int end = start + len;
    double* x = dotstr.a;
    double* y = dotstr.b;

    printf("Thread %ld started...\n", offset);

    double mysum;
    mysum = 0;
    int i;
    for(i=start; i<end; ++i) {
        mysum += (x[i]*y[i]);
    }   

    pthread_mutex_lock(&mutexsum);
    dotstr.sum += mysum;    
    pthread_mutex_unlock(&mutexsum);

    pthread_exit((void*)0);
}


int main(int argc, char* argv[]) 
{
    double *a = (double*)malloc(NUMTHRDS*VECLEN*sizeof(double));
    double *b = (double*)malloc(NUMTHRDS*VECLEN*sizeof(double));

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

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

    pthread_mutex_init(&mutexsum, NULL);

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for(i=0; i<NUMTHRDS; ++i) {
        pthread_create(&callThd[i], &attr, dotprod, (void*)i);
    }

    pthread_attr_destroy(&attr);

    for(i=0; i<NUMTHRDS; ++i) {
        pthread_join(callThd[i], NULL);
    }

    printf("Sum = %f\n", dotstr.sum);

    free(a);
    free(b);

    pthread_mutex_destroy(&mutexsum);
    pthread_exit(NULL);

}

運行效果:

這裏寫圖片描述

Condition variable

條件變量是另一種實現線程同步的方法,經常和 Mutex 配合使用。常見的應用場景如下:
這裏寫圖片描述
有關條件變量的操作也與 Mutex 類似:

pthread_cond_init (condition,attr)
pthread_cond_destroy (condition)
pthread_condattr_init (attr)
pthread_condattr_destroy (attr)
pthread_cond_wait (condition,mutex)
pthread_cond_signal (condition)
pthread_cond_broadcast (condition)

這裏需要注意的是pthread_cond_wait函數,調用該函數後,線程會阻塞至接收到指定條件的信號爲止。而且在調用函數前應該對 Mutex 加鎖,在pthread_cond_wait 函數等待期間線程會對 Mutex自動解鎖,以便其它線程能夠進入臨界區,修改條件,在收到指定條件的信號後,線程自動對 Mutex加鎖。簡而言之:該函數涉及解鎖-等待條件信號-加鎖三個過程。
在等待條件變化時,推薦使用 WHILE 循環:
1.多個線程等待同一信號時,它們將依次獲得 Mutex,然後修改等待條件
2.避免程序收到因程序 Bug 而產生的錯誤信號
3.Pthread 庫可能產生虛假的喚醒信號
以下是一個示例程序:

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define NUMTHREADS 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) {
    long my_id = (long)t;
    int i;
    for(i=0; i<TCOUNT; ++i) {
        pthread_mutex_lock(&count_mutex);
        count++;
        if(COUNT_LIMIT == count) {
            pthread_cond_signal(&count_threshold_cv);
            printf("inc_count(): thread %ld, count = %d Threshold reached.\n", my_id, count);
        }
            printf("inc_count(): thread %ld, count = %d Unlocking mutex.\n", my_id, count);
        pthread_mutex_unlock(&count_mutex);
        sleep(1);
    }

    pthread_exit(NULL);
}


void *watch_count(void* t) {
    long my_id = (long)t;
    printf("Starting watch_count(): thread %ld\n", my_id);

    pthread_mutex_lock(&count_mutex);
    while(count<COUNT_LIMIT) {
        pthread_cond_wait(&count_threshold_cv, &count_mutex);
        printf("watch_count(): thread %ld Condition signal received.\n", my_id);
    }
    count += 125;
    printf("watch_count(): thread %ld count now = %d.\n", my_id, count);    
    pthread_mutex_unlock(&count_mutex);

    pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&count_mutex, NULL);
    pthread_cond_init(&count_threshold_cv, NULL);

    long t1=1, t2=2, t3=3;
    pthread_t tid[3];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&tid[0], &attr, watch_count, (void*)t1);
    pthread_create(&tid[1], &attr, inc_count, (void*)t2);
    pthread_create(&tid[2], &attr, inc_count, (void*)t3);

    pthread_attr_destroy(&attr);

    long i;
    for(i=0; i<NUMTHREADS; ++i)
        pthread_join(tid[i], NULL);

    printf("Main(): Waited on %d threads to finish.\n", NUMTHREADS);

    pthread_mutex_destroy(&count_mutex);
    pthread_cond_destroy(&count_threshold_cv);
    pthread_exit(NULL);
}

運行效果如下:

這裏寫圖片描述

在瞭解條件變量以後,我們可以對用條件變量實現上一篇博客裏的生產者消費者模型。代碼如下:

#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR(func, no) { \
        fprintf(stderr, "%s: %s\n", func, strerror(no)); \
        exit(EXIT_FAILURE); \
    }

#define DEFAULT_CONSUMER_CNT    1
#define DEFAULT_PRODUCER_CNT    3

static size_t consumer_cnt; // count of consumers
static size_t producer_cnt; // count of producers
static size_t cake_cnt;

static pthread_t* g_thread = NULL; // pointer to thread IDs

static pthread_mutex_t g_mutex; //mutex
static pthread_cond_t g_cond; // condition variable

void* consume(void* arg) {
    int id = (int)arg;

    while(1) {
        pthread_mutex_lock(&g_mutex);

        while(cake_cnt <= 0) { // Consumer must wait untill there is one cake at least
            printf("Consumer[%d] waiting for cakes\n", id);
            pthread_cond_wait(&g_cond, &g_mutex);
        }

        printf("Consumer[%d] consuming a cake\n", id);
        cake_cnt--;

        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }
    return NULL;
}


void* produce(void* arg) {
    int id = (int)arg;

    while(1) {
        pthread_mutex_lock(&g_mutex);

        ++cake_cnt;
        printf("Producer[%d] makes a cake...\n", id);
        pthread_cond_signal(&g_cond);

        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }

    return NULL;
}


int main(int argc, char** argv)
{
        consumer_cnt = DEFAULT_CONSUMER_CNT;
        producer_cnt = DEFAULT_PRODUCER_CNT;
        char* prog = argv[0];
        int ch; 
        while ((ch = getopt(argc, argv, "b:c:p:")) != -1) {
             switch (ch) {
             case 'c':
                     consumer_cnt = atoi(optarg);
                     break;
             case 'p':
                     producer_cnt = atoi(optarg);
                     break;
             case '?':
             default:
                     printf("Usage: %s [-p producer_cnt] [-c consumer_cnt]\n"
                                "\tdefault producer_cnt=3, consumer_cnt=1\n", prog);
                     exit(EXIT_FAILURE);
             }   
        }

    g_thread = (pthread_t*)malloc((consumer_cnt+producer_cnt)*sizeof(pthread_t));
    memset(g_thread, 0, (consumer_cnt+producer_cnt)*sizeof(pthread_t));


    pthread_mutex_init(&g_mutex, NULL);
    pthread_cond_init(&g_cond, NULL);

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    int i, ret;
    for(i=0; i<consumer_cnt; ++i) {
        ret = pthread_create(&g_thread[i], &attr, consume, (void*)i);
        if(ret) {
            ERROR("pthread_create", ret);
        }
    }

    for(i=0; i<producer_cnt; ++i) {
        ret = pthread_create(&g_thread[i+consumer_cnt], &attr, produce, (void*)i);
        if(ret) {
            ERROR("pthread_create", ret);
        }
    }

    for(i=0; i<consumer_cnt+producer_cnt; ++i) {
        ret = pthread_join(g_thread[i], NULL);
        if(ret) {
            ERROR("pthread_join", ret);
        }
    }

    pthread_mutex_destroy(&g_mutex);
    pthread_cond_destroy(&g_cond);
    free(g_thread);

    exit(EXIT_SUCCESS);
}

由於使用條件變量實現時採用無界緩衝區,故而在只需在當前 buffer 爲空時,讓消費者線程等待即可,所以實現起來與semaphore相比要簡單許多。運行效果:
這裏寫圖片描述
當然,你可以調整生產者和消費者的數量,觀察程序的不同結果,加深對該模型的理解。

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