线程同步 条件变量

 


补充文档

https://www.cnblogs.com/god-of-death/p/11452308.html

条件变量

锁的不足:只能给一个线程放行,剩下的还需要阻塞
条件变量:可以让多个线程访问临界区,但会出现混乱问题,仍需要和互斥锁搭配使用

为什么

使用场景复杂,适用于生产者消费者模型
多个生产者对应多个线程
多个消费者也对应多个线程

生产者生产满了之后,不能生产,使用条件变量进行阻塞,消费者进行消费
消费之后,条件变量通知生产者进行生产,
当消费完了,仓库为空,条件变量通知消费者停止消费
两者之间是一个相互协作的关系。

函数

变量定义

pthread_cond_t cond;

存放的信息:

  1. 被条件变量阻塞的线程信息

初始化和销毁

#include<pthread_h>
pthread_cond_t cond;
//初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
  const pthread_condattr_t *restrict attr);
//销毁
int pthread_cond_destroy(pthread_cond_t *attr);

cond:条件变量的地址
attr:属性

线程阻塞

//线程阻塞函数,哪个线程调用这个函数,哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 在阻塞线程时候,如果线程已经对互斥锁mutex上锁,那么会将这把锁打开,这样做是为了避免死锁
  • 当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个mutex互斥锁锁上,继续向下访问临界区
  1. 阻塞等待条件变量cond

  2. 释放已掌握的互斥锁(解锁互斥量)相当于ptherad_mutex_unlock(&mutex);

1、2两步为一个原子操作

  1. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

固定时间阻塞

// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

这个函数的前两个参数和pthread_cond_wait函数是一样的,第三个参数表示线程阻塞的时长,
但是需要额外注意一点:struct timespec这个结构体中记录的时间是从1971.1.1到某个时间点的时间,
总长度使用秒/纳秒表示。因此赋值方式相对要麻烦一点:

time_t mytim = time(NULL);	// 1970.1.1 0:0:0 到当前的总秒数
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = time(NULL) + 100;	// 线程阻塞100s
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);

生产者和消费者

场景描述:使用条件变量实现生产者和消费者模型,生产者有5个,往链表头部添加节点,消费者也有5个,删除链表头部的节点。

给出示例代码

#include<pthread.h>
#include<string.h>
#include<iostream>
#include<stdlib.h>
#include<bits/stdc++.h>

using namespace std;

pthread_cond_t cond1;
pthread_mutex_t mutex1;


//链表定义
struct Node
{
    int number;
    Node* next;
};
Node* head=NULL;

//生产者
void* produce(void* arg)
{
    while(1)
    {
        //加一个互斥锁
        pthread_mutex_lock(&mutex1);
        //创建一个新的节点,临界区,需要锁
        Node* newnode=new Node();
        newnode->number=rand()%1000;
        newnode->next=head;
        head=newnode;
        cout<<"生产者:"<<pthread_self()<<" number: "<<newnode->number<<endl;
        pthread_mutex_unlock(&mutex1);
        //在此唤醒消费者线程
        pthread_cond_broadcast(&cond1);
        sleep(rand()%3);
    }
    return NULL;
}
//消费者

void* consume(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex1);
        while(head==NULL)
        {
            //需要阻塞消费者线程
            pthread_cond_wait(&cond1,&mutex1);
            //需要生产者线程唤醒该线程

        }
        Node* node=head;
        cout<<"消费者:"<<pthread_self()<<" number: "<<node->number<<endl;
        head=head->next;
        delete node;
        pthread_mutex_unlock(&mutex1);
        sleep(rand()%3);
    }
    return NULL;
}

int main()
{
    //条件变量和互斥锁的创建
    pthread_mutex_init(&mutex1,NULL);
    pthread_cond_init(&cond1,NULL);

    //线程的创建
    pthread_t p[5],c[5];

    for(int i=0;i<5;i++)
    {
        pthread_create(&p[i],NULL,produce,NULL);
    }
    for(int i=0;i<5;i++)
    {
        pthread_create(&c[i],NULL,consume,NULL);
    }

    //锁的释放
    for(int i=0;i<5;i++)
    {
        pthread_join(p[i],NULL);
        pthread_join(c[i],NULL);
    }


    //锁的释放
    pthread_mutex_destroy(&mutex1);
    pthread_cond_destroy(&cond1);
}

代码分析:
假设消费者抢到了cpu的时间片

  1. 由于链表为空,消费者线程被阻塞到了pthread_cond_wait
  2. 其他的消费者线程被阻塞到了mutex_lock上
  3. 但是被cond_wait阻塞的时候,会被cond_wait给打开,解除阻塞的时候,会将互斥锁重新锁上
  4. 继续向下执行
posted @   LiviaYu  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示