条件变量必须配合着互斥锁使用,互斥锁查看-》线程同步(互斥锁)
条件变量是什么:
本身不是锁,满足某个条件,像加锁一样,造成阻塞,与互斥量配合,给多线程提供会所。
为什么要用条件变量:
在线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待其他线程唤醒他;一旦条件满足,线程就可以被唤醒,并且拿互斥锁去访问共享区。经过这中设计能让进程运行更稳定。
pthread_cond_t 用于创建条件变量
pthread_cond_init 用于初始化条件变量
pthread_cond_destroy 用于销毁条件变量
pthread_cond_wait 用于阻塞条件变量
pthread_cond_timedwait 用于计时阻塞条件变量
pthread_cond_signal 用于唤醒条件变量
pthread_cond_broadcast 用于广播所有条件变量
条件变量的创建和互斥锁一样,拥有两种创建方式:静态创建、动态创建
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
注意:PTHREAD_COND_INITIALIZER是一个常量
在代码中,唤醒和等待一般是配套使用的,就好比互斥锁中的拿锁和解锁的关系。
唤醒至少一个线程的条件变量
广播(唤醒全部线程的条件变量)
在这示例代码会建立一个对链表操作的生产者消费者模型。这一般只能有两个线程,如果需要多个线程参加,可以看后面的线程同步之信号量,生产者消费者模型如下图所示:
#include
#include
#include
//定义链表的节点
struct msg
{
struct msg *next;
int num;
};
//建立全局的一个头结点
struct msg *head;
//静态建立一个条件变量has_product,放在全局里
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
//静态建立一个互斥锁lock,放在全局里
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
//声明一下两个执行函数
void *producer(void *p);
void *consumer(void *p);
//主函数
int main(void)
{
pthread_t pid, cid;
//建立一个随机种子
srand(time(NULL));
//创建生产者线程和消费者线程
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
//回收两个线程
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
void *consumer(void *p)
{
//建立一个临时的链表的游标mp
struct msg *mp;
for (;;)
{
pthread_mutex_lock(&lock);
while (head == NULL)
{
//消费者线程拿到互斥锁后等待,并且把互斥锁让出来。
pthread_cond_wait(&has_product, &lock);
}
//链表移动
mp = head;
head = mp->next; pthread_mutex_unlock(&lock);
//打印当前节点的num
printf("Consume %d\n", mp->num);
//删除游标
free(mp);
//随机延时
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
for (;;)
{
//给节点开辟空间
mp = malloc(sizeof(struct msg));
//随机赋值num 0-1000的值
mp->num = rand() % 1000 + 1;
printf("Produce %d\n", mp->num);
//给互斥锁加锁(防止其他线程抢占)
pthread_mutex_lock(&lock);
//移动游标
mp->next = head;
head = mp;
//互斥锁解锁,让其他线程抢占
pthread_mutex_unlock(&lock);
//唤醒消费者线程
pthread_cond_signal(&has_product);
//随机睡眠
sleep(rand() % 5);
}
}
这里我重点说明一下几点,我自己在做项目中出现的错误:下面是我写的一个例子,为了测试条件变量是否能正常使用。想得到的效果是:生产者先 i +1后,消费者再 i +1。
按现在看来就是:没有给条件变量上条件
#include
#include
#include
//条件变量和互斥锁的创建
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int i=0; //定义一个全局变量来当作线程的间的共享数据
//消费者线程
void *consumer(void *p)
{
printf("消费者:消费者线程开始工作\n");
for (;;)
{
pthread_mutex_lock(&lock); //拿锁
pthread_cond_wait(&has_product, &lock); //这里我没有使用条件锁住条件变量,使用条件变量的等待函数,把锁让出去给其他线程
printf("消费者:i = %d \n",i);
i++;
pthread_mutex_unlock(&lock); //解锁
if(i>=15) //这里为了方便查看成果,我们将i超过一定数额后退出循环
{
break;
}
}
}
//生产者线程
void *producer(void *p)
{
printf("生产者:生产者线程开始工作\n");
for (;;)
{
pthread_mutex_lock(&lock); //拿锁
printf("生产者:i = %d \n",i);
i++;
pthread_mutex_unlock(&lock); //解锁
pthread_cond_signal(&has_product); //唤醒消费者
if(i>=15) //这里为了方便查看成果,我们将i超过一定数额后退出循环
{
break;
}
}
}
//主函数
int main(int argc, char *argv[])
{
pthread_t pid, cid; //创建两个线程
pthread_create(&pid, NULL, producer, NULL); //生产者线程,这里请注意,生产者线程在前,消费者在后
pthread_create(&cid, NULL, consumer, NULL); //消费者线程
pthread_join(pid, NULL);
pthread_join(cid, NULL);
while(1);
return 0;
}
在这代码中,我当时的思路是,生产者生产出了一个东西出来,唤醒消费者去工作。消费者拿到锁之后,把锁让出来,被唤醒后出来工作。这感觉逻辑没什么问题,但是我编译出来是这样的……
这很显然不是我想要的效果,生产者一直工作,唤醒消费者无效。这里我认为是,电脑跑太快,导致消费者线程还没被创建出来,生产者就跑完了
这现在是达到了我想要的结果,但是在一个服务器中,各种资源都是十分有限的,不可能说是使用sleep来让线程慢下来。
在这结果中,生产者还是跑飞了,我在一堆生产者中才找到一个消费者。我想就是消费者每次都是拿到锁就让出去了,那么这个条件变量(条件变量的等待函数pthread_cond_wait)就压根没起到作用。
#include
#include
#include
#define BUF_SIZE 10 //环形队列实际只能存 BUF_SIZE - 1 个
//环形队列
typedef struct Queue
{
int * BUF; //队列的数据
int front; //队列的头指针
int rear; //队列的尾指针
}QUEUE_t;
QUEUE_t my_queue; //创建一个全局的环形队列
int i=0; //用于测试
int flag=0;
//信号量和和互斥量的静态创建
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
//环形队列的初始化
void Queue_init(QUEUE_t *queue_q)
{
queue_q->BUF = (int *)malloc(sizeof(int)*BUF_SIZE);
if(queue_q->BUF != NULL)
{
queue_q->front = queue_q->rear = 0; //初始化头尾指针的位置
}
}
//判断环形队列是否为空
int Queue_if_empty(QUEUE_t *queue_q)
{
if(queue_q->front == queue_q->rear)
{
return 1;
}
else
{
return 0;
}
}
//判断环形队列是否为满
int Queue_if_full(QUEUE_t *queue_q)
{
if((queue_q->rear +1) % BUF_SIZE == queue_q->front)
{
return 1;
}
else
{
return 0;
}
}
//环形队列添加数据
void Queue_Add(QUEUE_t *queue_q , int value)
{
if(Queue_if_full(queue_q) != 1) //队列有空位
{
queue_q->BUF[queue_q->rear] = value;
queue_q->rear = (queue_q->rear + 1)%BUF_SIZE ;
}
}
//环形队列输出数据
void Queue_Out(QUEUE_t *queue_q , int *value)
{
if(Queue_if_empty(queue_q) != 1) //队列有数据
{
*value = queue_q->BUF[queue_q->front];
queue_q->front = (queue_q->front + 1)%BUF_SIZE ;
}
}
//消费者线程
void *consumer(void *p)
{
int value;
for (;;)
{
pthread_mutex_lock(&lock);//拿锁
while (Queue_if_empty(&my_queue) == 1) //环形队列为空
{
pthread_cond_wait(&has_product, &lock); //让出互斥锁
}
Queue_Out(&my_queue, &value);
printf("消费者:提取的数据为 i = %d\n",value);
pthread_mutex_unlock(&lock);
if(flag ==1)
{
flag = 0;
pthread_cond_signal(&has_product);
}
if(i>=100000)
{
break;
}
}
}
//生产者线程
void *producer(void *p)
{
for (;;)
{
pthread_mutex_lock(&lock);
while (Queue_if_full(&my_queue) == 1) //环形队列为满
{
flag = 1;
pthread_cond_wait(&has_product, &lock); //让出互斥锁
}
//生产一个产品
Queue_Add(&my_queue,++i);
printf("生产者:存入数据为 i = %d\n",i);
pthread_mutex_unlock(&lock);
//唤醒阻塞在条件变量上的吃货`
pthread_cond_signal(&has_product);
if(i>=100000)
{
break;
}
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
//初始化环形队列
Queue_init(&my_queue);
pthread_create(&cid, NULL, consumer, NULL);
pthread_create(&pid, NULL, producer, NULL);
//pthread_join(pid, NULL);
//printf("回收了生产者\n");
//pthread_join(cid, NULL);
//printf("回收了消费者\n");
while(1);
return 0;
}
由于这最后一次更改的代码量比较多,我这里总结一下主要更改内容和期间遇到的问题及解决方法:
主要更改内容:使用环形队列、给条件变量加上条件。
先说一下环形队列方面
再说一下条件变量方面的注意点