线程间同步互斥(3)条件变量使用

今来看看条件变量的使用!!!

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:

一个线程等待"条件变量的条件成立"而挂起
另一个线程使 “条件成立”(给出条件成立信号)

条件的检测是在互斥锁的保护下进行的。线程在改变条件状态之前必须首先锁住互斥量。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。

条件变量使用步骤:

1. 初始化:init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER;属性置为NULL;

2. 等待条件成立:pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真 timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait);

3. 激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)

4. 清除条件变量:destroy;无线程等待,否则返回EBUSY清除条件变量:destroy;无线程等待,否则返回EBUSY

#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);

// 阻塞等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);

// 超时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

// 解除所有线程的阻塞
int pthread_cond_destroy(pthread_cond_t *cond);

// 至少唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);  

接下来举个例子:

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。

#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 *mp = NULL;  //节点指针
//利用宏定义的方式初始化全局的互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
 
void *producter(void *arg)
{
    while (1) 
	{
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 400 + 1;
        printf("---producted---%d\n", mp->num);
 
        pthread_mutex_lock(&mutex);//访问共享区域必须加锁
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&mutex);
 
        pthread_cond_signal(&has_product);//通知消费者来消费
		
        sleep(rand() % 3);
    }
 
    return NULL;
}
 
void *consumer(void *arg)
{
    while (1)
	{
        pthread_mutex_lock(&mutex);//访问共享区域必须加锁
        while (head == NULL)//如果共享区域没有数据,则解锁并等待条件变量
	{
            pthread_cond_wait(&has_product, &mutex);
        }
        mp = head;
        head = mp->next;
        pthread_mutex_unlock(&mutex);
 
        printf("------------------consumer--%d\n", mp->num);
        free(mp); //释放被删除的节点内存
        mp = NULL;//并将删除的节点指针指向NULL,防止野指针
		
        sleep(rand() % 3);
    }
 
    return NULL;
}
 
int main(void)
{
    pthread_t ptid, ctid;
 
    //创建生产者和消费者线程
    pthread_create(&ptid, NULL, producter, NULL);
    pthread_create(&ctid, NULL, consumer, NULL);
    //主线程回收两个子线程
    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);
 
    return 0;
}

相较于mutex而言,条件变量可以减少竞争,提高效率。

在上边这个例子中有两点注意:

1、为什么pthread_cond_wait需要加锁??

pthread_cond_wait中的mutex用于保护条件变量,调用这个函数进行等待条件的发生时,mutex会被自动释放,以供其它线程(生产者)改变条件,pthread_cond_wait中的两个步骤必须是原子性的,也就是说必须把把调用线程放到条件等待队列上和释放mutex两个步骤捆绑到一起。
不然上面的两个步骤中间就可能插入其它操作。比如,如果先释放mutex,这时候生产者线程向队列中添加数据,然后signal,之后消费者线程才去『把调用线程放到等待队列上』,signal信号就这样被丢失了。
如果先把调用线程放到条件等待队列上,这时候另外一个线程发送了pthread_cond_signal(我们知道这个函数的调用是不需要mutex的),然后调用线程立即获取mutex,两次获取mutex会产生deadlock.

2、为什么pthread_cond_wait前边的判断要用while,不用if??

这是因为可能会存在虚假唤醒”spurious wakeup”的情况。也就是说,即使没有线程调用condition_signal, 原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。
虚假唤醒在linux的多处理器系统中/在程序接收到信号时可能回发生。在Windows系统和JAVA虚拟机上也存在。在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值