条件变量给了线程以无竞争的方式等待特定条件发生。条件变量是和互斥量一起使用的,条件变量是由互斥量保护的。这么讲,大家可能不明白,这条件变量有什么用?干什么的?还是结合pthread_cond_wait()函数来分析一下吧!
下面给出本文讲使用的的有关条件变量的函数。
单刀直入,我们需要分析的重点就是pthread_cond_wait()函数。而pthread_cond_timewait()只是比它多了个超时而已。
pthread_cond_wait()函数等待条件变量变为真的。它需要两个参数,第一个参数就是条件变量,而第二个参数mutex是保护条件变量的互斥量。也就是说这个函数在使用的时候需要配合pthread_mutex_lock()一起使用。即:
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
因此,这个函数的功能可以总结如下:
这个pthread_cond_wait()函数可以被pthread_cond_signal()或者是pthread_cond_broadcast()函数唤醒。不同之处在于,pthread_cond_signal()可以唤醒至少一个线程;而pthread_cond_broadcast()则是唤醒等待该条件满足的所有线程。在使用的时候需要注意,一定是在改变了条件状态以后再给线程发信号。
pthread_cond_init()函数是用来初始化pthread_cond_t类型的条件变量的,和之前的函数类似,在动态分配pthread_cond_t类型的变量的时候,只能使用pthread_cond_init它来初始化(因为POSIX标准只规定了接口长什么样子,没规定怎么实现,所以pthread_cond_t这个数据类型可能被实现为结构体,为了最大化可移植性,就搞了个init函数来动态初始化)。
pthread_cond_destroy()函数就是用来释放条件变量的(反初始化),再次提醒,不是释放内存空间的。
pthread_cond_timedwait()函数和 pthread_cond_wait()函数比起来多一个时间参数,这个参数可以指定等待这个条件多长时间,他是通过timespec结构指定。所以用起来比较麻烦,不在这里细述。
好了,关于这些函数就介绍完了,下面看一段代码,这段代码将演示一个非常经典的同步问题:生产者——消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//链表的结点
struct msg
{
int num;
struct msg *next;
};
struct msg *head = NULL; //头指针
struct msg *temp = NULL; //节点指针
//静态方式初始化互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_producer = PTHREAD_COND_INITIALIZER;
void *producer(void *arg)
{
while (1) //线程正常不会解锁,除非收到终止信号
{
pthread_mutex_lock(&mutex); //加锁
temp = malloc(sizeof(struct msg));
temp->num = rand() % 100 + 1;
temp->next = head;
head = temp; //头插法
printf("---producered---%d\n", temp->num);
pthread_mutex_unlock(&mutex); //解锁
pthread_cond_signal(&has_producer); //唤醒消费者线程
usleep(rand() % 3000); //为了使该线程放弃cpu,让结果看起来更加明显。
}
return NULL;
}
void *consumer(void *arg)
{
while (1) //线程正常不会解锁,除非收到终止信号
{
pthread_mutex_lock(&mutex); //加锁
while (head == NULL) //如果共享区域没有数据,则解锁并等待条件变量
{
pthread_cond_wait(&has_producer, &mutex); //我们通常在一个循环内使用该函数
}
temp = head;
head = temp->next;
printf("------------------consumer--%d\n", temp->num);
free(temp); //删除节点,头删法
temp = NULL; //防止野指针
pthread_mutex_unlock(&mutex); //解锁
usleep(rand() % 3000); //为了使该线程放弃cpu,让结果看起来更加明显。
}
return NULL;
}
int main(void)
{
pthread_t ptid, ctid;
srand(time(NULL)); //根据时间摇一个随机数种子
//创建生产者和消费者线程
pthread_create(&ptid, NULL, producer, NULL);
pthread_create(&ctid, NULL, consumer, NULL);
//主线程回收两个子线程
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
return 0;
}
程序运行结果如下所示:
这是截取的一部分运行结果,需要注意的是,malloc()放到了临界区,free()也被放到了临界区。这是因为可能在两个线程中分别执行对同一块内存的申请和释放。这就会出错。所以说,在评估锁的粒度的时候,一定要小心,并且谨慎。