千锋教育-做有情怀、有良心、有品质的职业教育机构

400-811-9990
手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

上海
  • 北京
  • 郑州
  • 武汉
  • 成都
  • 西安
  • 沈阳
  • 广州
  • 南京
  • 深圳
  • 大连
  • 青岛
  • 杭州
  • 重庆
当前位置:南昌千锋IT培训  >  技术干货  >  为什么pthread_cond_wait需要互斥锁mutex作为参数?

为什么pthread_cond_wait需要互斥锁mutex作为参数?

来源:千锋教育
发布人:xqq
时间: 2023-10-15 03:11:07

一、pthread_cond_wait需要互斥锁mutex作为参数的原因

pthread_cond_wait() 是 POSIX 线程库中的一个条件变量操作函数,它会等待条件变量的信号并使线程进入睡眠状态。

通常的应用场景下,当前线程执行pthread_cond_wait时,处于临界区访问共享资源,存在一个mutex与该临界区相关联,这是理解pthread_cond_wait带有mutex参数的关键。当前线程执行pthread_cond_wait前,已经获得了和临界区相关联的mutex;执行pthread_cond_wait会阻塞,但是在进入阻塞状态前,必须释放已经获得的mutex,让其它线程能够进入临界区。当前线程执行pthread_cond_wait后,阻塞等待的条件满足,条件满足时会被唤醒;被唤醒后,仍然处于临界区,因此被唤醒后必须再次获得和临界区相关联的mutex。

综上,调用pthread_cond_wait时,线程总是位于某个临界区,该临界区与mutex相关,pthread_cond_wait需要带有一个参数mutex,用于释放和再次获取mutex。

二、使用pthread_cond_wait(cond, mutex)解决生产者和消费者问题

接下来通过一个具体的应用场景来说明,为什么pthread_cond_wait需要一个看似多余的mutex参数。

1、生产者和消费者问题

存在一个共享缓冲区,生产者向共享缓冲区写入数据,消费者从共享缓冲区中读取数据。生产者和消费者存在同步关系:当共享缓冲区为满时,生产者需要等待,等待消费者从共享缓冲区取走数据;当共享缓冲区为空时,消费者需要等待,等待生产者向共享缓冲区中写入数据。

使用环形队列实现共享缓冲区,数据结构如下:

#define CAPACITY 8     // 缓冲区的最大容量int buffer[CAPACITY];  // 缓冲区数组int in;                // 缓冲区的写指针int out;               // 缓冲区的读指针int size;              // 缓冲区中的数据个数

缓冲区的相关代码如下:

void buffer_init(){    in = 0;    out = 0;    size = 0;}// 判断缓冲区是否为空int buffer_is_empty(){    return size == 0; }// 判断缓冲区是否为满int buffer_is_full(){    return size == CAPACITY; }// 向缓冲区中追加一个数据void buffer_put(int item){    buffer[in] = item;    in = (in + 1) % CAPACITY;    size++;}// 从缓冲区中取走一个数据int buffer_get(){    int item;    item = buffer[out];    out = (out + 1) % CAPACITY;    size--;    return item;}

如果存在多个生产者和多个消费者,变量in、out和size会被它们共享访问,因此生产者和消费者还存在互斥关系:

当某个生产者执行buffer_is_full、buffer_put时,访问了变量in、out和size,只能允许该生产者独占访问这三个变量,禁止其他生产者和消费者访问这些共享变量。当某个消费者执行buffer_is_empty、buffer_get时,访问了变量in、out和size,只能允许该消费者独占访问这三个变量,禁止其他生产者和消费者访问这些共享变量。

2、使用pthread_cond_wait(cond)解决生产者和消费者问题(名列前茅版)

下面我们尝试使用没有mutex参数的pthread_cond_wait来模拟生产者消费者,假想中没有mutex参数的pthread_cond_wait原型如下:

int pthread_cond_wait(pthread_cond_t *cond);

pthread_cond_wait(cond)的功能非常简单,仅仅阻塞当前线程。在生产者消费者这个应用场景中,很快就能发现pthread_cond_wait(cond)的问题。

使用pthread_cond_wait(cond)解决生产者和消费者问题的代码如下:

// 生产者线程执行的流程void producer_loop(){    int i;    // 生产CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为满时,生产者需要等待        while (buffer_is_full()) {               // 当前线程已经持有了mutex,调用pthread_cond_wait阻塞,必然导致死锁            pthread_cond_wait(&cond);        }        // 此时,缓冲区肯定不是满的,向缓冲区写数据        buffer_put(i);        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}// 消费者线程执行的流程void consumer_loop(){    int i;    // 消费CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为空时,消费者需要等待        while (buffer_is_empty()) {               // 当前线程已经持有了mutex,调用pthread_cond_wait阻塞,必然导致死锁            pthread_cond_wait(&cond);        }        // 此时,缓冲区肯定不是空的,从缓冲区取数据        int item = buffer_get();        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}

以上程序存在一个会导致死锁的严重错误,以生产者为例:

当前缓冲区已经满了,生产者运行,首先获取mutex然后检测buffer_is_full为真,生产者无法放入数据调用pthread_cond_wait,该生产者进入阻塞状态,等待被消费者唤醒消费者试图获取mutex,由于mutex已经被占用了,消费者将进入阻塞状态生产者和消费者均进入阻塞状态,系统死锁

3、使用pthread_cond_wait(cond)解决生产者和消费者问题(第二版)

为了解决死锁的问题,需要对上一节的程序进行如下改进:

调用线程调用pthread_cond_wait(cond)前,已经持有了mutex执行pthread_cond_wait(cond)前,调用pthread_unlock(mutex)释放mutex执行pthread_cond_wait(cond)后,调用pthread_lock(mutex)再次获得mutex
// 生产者线程执行的流程void producer_loop(){    int i;    // 生产CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为满时,生产者需要等待        while (buffer_is_full()) {               pthread_mutex_unlock(&mutex);            pthread_cond_wait(&cond);            pthread_mutex_lock(&mutex);        }        // 此时,缓冲区肯定不是满的,向缓冲区写数据        buffer_put(i);        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}// 消费者线程执行的流程void consumer_loop(){    int i;    // 消费CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为空时,消费者需要等待        while (buffer_is_empty()) {               pthread_mutex_unlock(&mutex);            pthread_cond_wait(&cond);            pthread_mutex_lock(&mutex);                    }        // 此时,缓冲区肯定不是空的,从缓冲区取数据        int item = buffer_get();        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}

这里解释一下为什么线程调用pthread_cond_wait返回后,需要再次调用pthread_mutex_lock获取锁。以生产者为例,以下为生产者向buffer中追加数据的代码段:

// 生产者向buffer中追加数据pthread_mutex_lock(&mutex);while (buffer_is_full()) {       pthread_mutex_unlock(&mutex);    pthread_cond_wait(&cond);    pthread_mutex_lock(&mutex);}buffer_put(i);pthread_mutex_unlock(&mutex);

在上面这段代码中,生产者线程会调用buffer_is_full和buffer_put,访问共享变量in、out和size。必须保证线程以独占的方式访问这些共享变量,即线程在调用buffer_is_full和buffer_put前必须持有锁。线程从pthread_cond_wait返回后,调用pthread_mutex_lock再次获得锁,然后执行语句while (buffer_is_full())时,因为已经拥有了锁,所以通过buffer_is_full访问共享变量是安全的。

4、使用pthread_cond_wait(cond, mutex)解决生产者和消费者问题

在上一个版本的程序中,生产者和消费者中存在如下代码段:

// 先释放mutex、再阻塞、最后再次获取mutexpthread_mutex_unlock(&mutex);pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);

从这个应用场景来看,pthread_cond_wait被设计为带有mutex参数,用一次函数调用pthread_cond_wait(cond, mtex)即可实现以上三条语句的功能。

使用pthread_cond_wait(cond, mutex)解决生产者和消费者问题的代码如下:

// 生产者线程执行的流程void producer_loop(){    int i;    // 生产CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为满时,生产者需要等待        while (buffer_is_full()) {               // 当前线程已经持有了mutex,首先释放mutex,然后阻塞,醒来后再次获取mutex                        pthread_cond_wait(&cond, &mutex);        }        // 此时,缓冲区肯定不是满的,向缓冲区写数据        buffer_put(i);        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}// 消费者线程执行的流程void consumer_loop(){    int i;    // 消费CAPACITY*2个数据    for (i = 0; i < CAPACITY*2; i++) {          pthread_mutex_lock(&mutex);        // 当缓冲区为空时,消费者需要等待        while (buffer_is_empty()) {              // 当前线程已经持有了mutex,首先释放mutex,然后阻塞,醒来后再次获取mutex             pthread_cond_wait(&cond, &mutex);        }        // 此时,缓冲区肯定不是空的,从缓冲区取数据        int item = buffer_get();        pthread_mutex_unlock(&mutex);                // 缓冲区的状态发生了变化,唤醒其它的生产者或消费者        pthread_cond_signal(&cond);    }}

延伸阅读1:pthread_cond_wait()函数简介

pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。

声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。

猜你喜欢LIKE

为什么Impala要使用C++语言,而不是Java?

2023-10-15

为什么Cassandra的写速度比MySQL快?

2023-10-15

为什么不能在前端连接数据库?

2023-10-15

最新文章NEW

为什么使用Redis做缓存而不会使用关系型数据库?

2023-10-15

假设mysql的两条连接同时发送对同一个表同一条记录的update语句,mysql会怎么处理?

2023-10-15

手机APP开发适合哪些行业?

2023-10-15

相关推荐HOT

更多>>

快速通道 更多>>

最新开班信息 更多>>

网友热搜 更多>>