前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程同步之条件变量(pthread_cond_wait)

线程同步之条件变量(pthread_cond_wait)

作者头像
zy010101
发布2020-05-18 15:51:57
18.4K2
发布2020-05-18 15:51:57
举报
文章被收录于专栏:程序员

条件变量

条件变量给了线程以无竞争的方式等待特定条件发生。条件变量是和互斥量一起使用的,条件变量是由互斥量保护的。这么讲,大家可能不明白,这条件变量有什么用?干什么的?还是结合pthread_cond_wait()函数来分析一下吧!

下面给出本文讲使用的的有关条件变量的函数。

单刀直入,我们需要分析的重点就是pthread_cond_wait()函数。而pthread_cond_timewait()只是比它多了个超时而已。

pthread_cond_wait()函数等待条件变量变为真的。它需要两个参数,第一个参数就是条件变量,而第二个参数mutex是保护条件变量的互斥量。也就是说这个函数在使用的时候需要配合pthread_mutex_lock()一起使用。即:

代码语言:javascript
复制
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);

因此,这个函数的功能可以总结如下:

  1. 等待条件变量满足;
  2. 把获得的锁释放掉;(注意:1,2两步是一个原子操作) 当然如果条件满足了,那么就不需要释放锁。所以释放锁这一步和等待条件满足一定是一起执行(指原子操作)。
  3. pthread_cond_wait()被唤醒时,它解除阻塞,并且尝试获取锁(不一定拿到锁)。因此,一般在使用的时候都是在一个循环里使用pthread_cond_wait()函数,因为它在返回的时候不一定能拿到锁(这可能会发生饿死情形,当然这取决于操作系统的调度策略)。

这个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结构指定。所以用起来比较麻烦,不在这里细述。

好了,关于这些函数就介绍完了,下面看一段代码,这段代码将演示一个非常经典的同步问题:生产者——消费者模型

代码语言:javascript
复制
#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()也被放到了临界区。这是因为可能在两个线程中分别执行对同一块内存的申请和释放。这就会出错。所以说,在评估锁的粒度的时候,一定要小心,并且谨慎。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/05/10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 条件变量
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档