线程同步-条件变量

2024-08-24 13:47:28 浏览数 (2)

文章目录

  • 引言
  • 条件变量
    • 初始化条件变量:pthread_cond_init
    • 销毁条件变量:pthread_cond_destroy
    • 条件等待:pthread_cond_wait
    • 唤醒等待:pthread_cond_signal、pthread_cond_broadcast
    • 认识条件变量
    • 接口使用

引言

有一个非常好的VIP自习室,一次只允许一个人进来,每一个自习完成的同学归还钥匙后,不能立马申请,第二次申请必须排队,也就是说其他人也必须排队,这种去自习室自习有一定的顺序性,这称之为同步。

换言之,每一个线程在访问临界资源时,有一定的顺序性,这称之为线程的同步。这里的顺序性可以是严格的顺序性,也可以是宏观上的具有相对顺序性。

条件变量

一个条件变量是一个pthread_cond_t类型

初始化条件变量:pthread_cond_init

代码语言:javascript复制
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);

cond:要初始化的条件变量 attrNULL

销毁条件变量:pthread_cond_destroy

代码语言:javascript复制
int pthread_cond_destroy(pthread_cond_t *cond)

在调用 pthread_cond_destroy 之前,确保没有线程在等待这个条件变量

条件等待:pthread_cond_wait

代码语言:javascript复制
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

cond:要在这个条件变量上等待 mutex:互斥量

唤醒等待:pthread_cond_signal、pthread_cond_broadcast

唤醒一个线程:

代码语言:javascript复制
int pthread_cond_signal(pthread_cond_t *cond);

唤醒所有线程:

代码语言:javascript复制
int pthread_cond_broadcast(pthread_cond_t *cond);

认识条件变量

有两个人分别是A和B,B往箱子中放苹果,A从箱子中拿苹果,A必须在B放完后拿苹果。箱子实际上是一个共享资源,苹果相当于数据,当B没有往共享资源中写数据时,A进行读取,那么读取的内容就是垃圾数据。A,B在进行操作的时候,必须保证箱子(共享资源)的安全,因此需要加锁pthread_mutex_lock ,完成之后进行解锁pthread_mutex_unlock。注定A,B需要互斥式的访问箱子(共享资源),这样才能保证B正在写的时候,A不会读取。A需要先检测箱子(共享资源)重是否有苹果(数据),当检测到没有苹果(数据),A需要解锁退出,然后A立马重新申请锁,再去检测,因此A可能在不断申请锁、解开锁。A如此频繁的申请锁,B可能抢不到锁。因此这种做法不合理:A、B互斥,如果B竞争能力弱的话,B抢不到锁,A只能频繁申请锁、解开锁。

为了解决这个矛盾,在上述场景中,引入一个铃铛,当A检测到箱子(共享资源)中没有苹果(数据),A解开锁之后不要再去申请锁,而是去铃铛那里等待铃铛,B放完苹果后摇铃铛,听到铃铛响了,A再去申请锁。

这里引入的铃铛就是条件变量,条件变量必须提供两个东西:

  1. 需要一个线程队列
  2. 需要有通知机制

此时又来一个C,也是来拿苹果,A和C就会形成竞争了,铃铛想起的时候,就会把A和C都唤醒,这就是pthread_cond_broadcast

接口使用

代码语言:javascript复制
#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>

const int num=5;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;

void *Wait(void *args)
{
    std::string name=static_cast<const char*>(args);
    while (true)
    {
        pthread_mutex_lock(&gmutex);
        //条件等待
        pthread_cond_wait(&gcond,&gmutex);
        usleep(10000);
        std::cout<<"I am : "<<name<<std::endl;
        
        pthread_mutex_unlock(&gmutex);
        //usleep(100000);
    }
    
}

int main()
{
    pthread_t threads[num];
    for(int i=0;i<num;i  )
    {
        char *name=new char[1024];
        snprintf(name,1024,"thread-%d",i 1);
        pthread_create(threads i,nullptr,Wait,(void *)name);
    }
    //唤醒线程
    while(true)
    {
        pthread_cond_signal(&gcond);   //一次唤醒一个线程
        //pthread_cond_broadcast(&gcond);   //一次唤醒多个线程
        std::cout<<"唤醒一个线程..."<<std::endl;
        sleep(2);
    }

    for(int i=0;i<num;i  )
    {
        pthread_join(threads[i],nullptr);
    }

    return 0;
}

为什么pthread_cond_wait在加锁和解锁之间使用?

确保条件检查的原子性:在多线程环境中,条件变量通常与互斥锁一起使用来保护共享资源。线程在检查条件之前需要持有锁,以避免其他线程修改共享资源。调用 pthread_cond_wait 时,函数会释放锁以让其他线程可以修改共享资源,然后在条件满足后重新获取锁,这样可以保证在条件变量被触发后,线程能够再次安全地检查条件和访问共享资源。 避免竞争条件:如果 pthread_cond_wait 不释放锁,那么其他线程将无法获取这个锁并修改条件,这可能导致死锁或线程无法继续工作。通过在 pthread_cond_wait 内部释放和重新获取锁,确保了条件检查的完整性和线程的正确同步。

0 人点赞