Pthread

来源:互联网 发布:php防止ajax重复提交 编辑:程序博客网 时间:2024/06/05 18:17

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 100DOTDATA 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 12int 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    3static size_t consumer_cnt; // count of consumersstatic size_t producer_cnt; // count of producersstatic size_t cake_cnt;static pthread_t* g_thread = NULL; // pointer to thread IDsstatic pthread_mutex_t g_mutex; //mutexstatic pthread_cond_t g_cond; // condition variablevoid* 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相比要简单许多。运行效果:
这里写图片描述
当然,你可以调整生产者和消费者的数量,观察程序的不同结果,加深对该模型的理解。

0 0
原创粉丝点击