为什么要有条件变量:为了弥补互斥锁的不足
互斥锁的不足:线程只有持锁执行,无锁阻塞(pthread_mutex_lock)两种状态,而如果某个线程是 根据 共享数据的某个变化是否发生 作为依据 来做其他操作。那么如果用互斥锁来实现的话,那么该线程会重复如下操作:
申请持锁成功
判断 (共享数据的某个变化是否发生)
如果有发生,执行其他操作
如果没发生,解锁,(加sleep()会影响效率)并重新申请持锁
申请持锁失败 阻塞(pthread_mutex_lock) 等待持锁
如果 共享数据的某个变化 需要比较久才发生,那么 该线程就会反复执行上面的操作,造成CPU处理时间的浪费。有人会说可以在改伪代码中加 sleep()节省CPU处理时间,但是这样的后果是,不能保证该进程以最快的速度 查到 共享数据的某个变化,影响效率。即类似情况,互斥锁,会有时间和效率的问题。
所以 为了解决 上述时间和效率问题,引入 条件变量
条件变量:
条件变量允许线程阻塞 并 等待其他线程给自己发送信号唤醒自己。兼顾CPU处理时间和效率,这样就弥补了互斥锁的不足。
相关函数:
pthread_cond_destroy()
pthread_cond_init()
pthread_cond_broadcast()
pthread_cond_signal()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_cond_t 类型,条件变量类型
NAME
pthread_cond_destroy, pthread_cond_init — destroy and initialize condition variables
SYNOPSIS
#include <pthread.h>
//销毁
int pthread_cond_destroy(pthread_cond_t *cond);
//动态初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
//静态初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
NAME
pthread_cond_broadcast, pthread_cond_signal — broadcast or signal a condition 广播或发出条件的信号
SYNOPSIS
#include <pthread.h>
//广播形式的唤醒,唤醒所有因 该条件变量cond而阻塞的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒 因为该条件变量cond而阻塞的所有线程中的任意一个线程
int pthread_cond_signal(pthread_cond_t *cond);
NAME
pthread_cond_timedwait, pthread_cond_wait — wait on a condition
SYNOPSIS
#include <pthread.h>
//非阻塞等待条件变量变为真,超时等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
//阻塞等待条件变量变为真,参数:条件变量,互斥量
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
pthread_cond_wait() 用于阻塞线程 并 将线程挂载到 cond条件变量的 阻塞线程队列中,然后解锁,最后等待唤醒。被唤醒之后会抢锁,如果只有自己一个阻塞线程,则被唤醒后 直接持锁。
使用pthread_cond_wait方式如下:
1 pthread _mutex_lock(&mutex)
2 while或if(线程执行的条件是否成立)
pthread_cond_wait(&cond, &mutex);
3 线程执行
4 pthread_mutex_unlock(&mutex);
问题一 : 为什么要先持锁
答:条件变量是为了弥补互斥锁的不足,两者都是为了 同一时间只能有一个线程访问临界资源,所以必然要加锁。
问题二:pthread_cond_wait() 怎么防止 持锁阻塞,即如果在没有解锁的条件下进入阻塞状态,这一行的话,其他线程将无法访问临界资源。
答:pthread_cond_wait() 内部的实现可以理解为,先将线程挂载到 阻塞队列中,然后解锁。所以在函数内部,已经解锁了。至于函数内部解锁的时机是很重要的,函数是在将 线程挂载到阻塞队列之后 才解锁。这样做的目的是,保证在将线程挂载到阻塞队列之前,其他线程无法改变临界资源。试想一下,如果先解锁,再挂载,会发生什么情况,如果解锁之后,其他线程拿到锁,并改变了临界资源,并发送信号 pthread_cond_signal(), 就会导致 本线程无法响应本次 临界资源的变化。
情景:
线程持锁,并判断条件是否成立,不成立的话,pthread_cond_wait() 阻塞线程 并 将线程挂载到 cond条件变量的 阻塞线程队列中,然后解锁,最后等待唤醒。pthread_cond_wait() 被唤醒之后会抢锁,如果只有自己一个阻塞线程,则被唤醒后 直接持锁, 并开始判断条件是否成立。。。。 成立的话 跳出循环,执行下一步。
实验1:
用 条件变量 通知法 实现 :规定时间内 四个线程 按顺序 分别不停的向终端 输出 a,b,c,d 每个线程输出一个字母
规范的写法:用条件变量实现 通知法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#define THRNUM 4
static int num = 0;
static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int next(int n)
{
if(n+1 == THRNUM)
return 0;
return n+1;
}
static void *thr_add(void *p)
{
int n = (int)p;//0 1 2 3
int c = 'a' + (int)p;
while(1)
{
pthread_mutex_lock(&mut);
while(num != n)//判断是不是目标下一个线程
pthread_cond_wait(&cond,&mut);//被唤醒的所有线程抢锁,抢到锁的线程 判断条件是否成立。。。
write(1,&c,1);
num = next(num);//准备唤醒下一个线程
pthread_cond_broadcast(&cond);//惊群
pthread_mutex_unlock(&mut);
}
pthread_exit(NULL);
}
int main()
{
pthread_t tid[THRNUM];
int i,err;
for(i = 0; i < THRNUM; i++)
{
err = pthread_create(tid+i,NULL,thr_add,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create(): %s\n",strerror(err));
exit(1);
}
}
//alarm(5);
for(i = 0;i < THRNUM; i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mut);
pthread_cond_destroy(&cond);
exit(0);
}