条件变量:
条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
主要应用函数:
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型 用于定义条件变量
pthread_cond_t cond;
pthread_cond_init函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
-
阻塞等待条件变量cond(参1)满足
-
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
-
当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
pthread_cond_timedwait函数
限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参3: 参看man sem_timedwait函数,查看struct timespec结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参考APUE.11.6线程同步条件变量小节
在讲解setitimer函数时我们还提到另外一种时间类型:
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct my_pth
{
struct my_pth *next;
int num;
}my_pth;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t my_produc = PTHREAD_COND_INITIALIZER;
my_pth *head;
void * produc(void *p)
{
my_pth *temp = NULL;
int num = (int)p;
while (1)
{
temp = (my_pth*)malloc(sizeof(my_pth));//生产
pthread_mutex_lock(&mutex);//加锁
temp->num = rand() % 50;
temp->next = head;
head = temp;
//这里在运行5次之后会得到一个有5个元素的链表,只不过,链表的元素是从后往前加的,即:最开始的元素在最后面。
printf("++++++++++++++++++++++++++++++++++++++++++++num=%d.我是%d号线程\n", temp->num, num);
pthread_mutex_unlock(&mutex);//解锁
pthread_cond_signal(&my_produc);//唤醒其他线程
sleep(1);
}
return NULL;
}
void *consumers(void *p)
{
my_pth *temp = NULL;
int num = (int)p;
while (1)
{
pthread_mutex_lock(&mutex);//pthread_cond_wait要解锁,这里自然要加锁
while (head == NULL)
{
pthread_cond_wait(&my_produc, &mutex);//该函数解锁mutex,然后阻塞本线程,被唤醒后加锁mutex;
}
temp = head;
printf("------------num=%d.我是%d号线程\n", temp->num, num);
head = head->next;
free(temp);//消耗
pthread_mutex_unlock(&mutex);//解锁
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t pth[10];
for (int i = 0; i != 5; i++)
{
pthread_create(&pth[i], NULL, produc, (void*)i);
}
for (int i = 0; i != 5; i++)
{
pthread_create(&pth[i + 5], NULL, consumers, (void*)i);
}
sleep(3);
for (int i = 0; i != 10; i++)
{
pthread_cancel(pth[i]);
pthread_join(pth[i], NULL);
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&my_produc);
return 0;
}
编译后运行:,因为直接输出到屏幕会浪费造成错乱,原因我在《一秒钟你的电脑上能数多少数》中说明了的,所以我将其输入到文件中。观察结果会发现写和读时对应的,基本上呈现一种对称关系。这取决于链表的生成顺序。这里要注意的是,因为在主线程中是先生成生产者的,生产者中是有互斥量对线程加解锁的,这就保证了链表的生成是不会错乱的。这个在结果中也是体现了的。要注意的是:链表是最开始的元素在最后。所以才有这样的读顺序。
现在我们将生产者的数目减少位3个,看看结果如何:。惊不惊喜,意不意外?先不说阻塞的事儿,先看看结果,我这次没有输出到文件中,还好没输出到文件中,不然我都不知道怎么回事。还是挺好的,没有抢食的情况,生产一个菜消费一个,有序,不乱。虽然3号4号消费者机会相对少一些。所以总的来说还是好的,就是为嘛3秒后没有主动结束程序啊?不是在主线程中调用了pthread_cancel函数么?为嘛没杀死?简单一分析我们就能够知道,这玩意肯定是生产者先死了,消费者发现head==NULL才阻塞在那里。毕竟当生产者消费者数量一样的时候没出现这样的情况。但是我有怕pthread_cancel函数啊。其实原因也简单,就是因为它阻塞在那里了。没有进入内核,没有系统调发生。所以怕pthread_cancel没杀死。然后线程又一直阻塞在那里,等待被唤醒。这就像pause函数没有接收到alarm函数的信号一样。一直被阻塞。最后只能强制杀死,说到这里,我们能不能用kill函数杀死线程?不知道,一个是线程,一个是进程,不知道,留着等以后填坑。
条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。
如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。