【Linux】线程篇---线程安全&&生产者消费者模型

2023-05-16

目录

1.线程安全概念

2.互斥的实现

2.1互斥锁

2.2互斥锁原子性的保证

2.3互斥锁接口

2.3.1初始化互斥锁

2.3.2互斥锁加锁接口

2.3.3解锁接口

2.3.4 销毁互斥锁接口

2.4代码验证锁的接口

3.同步的实现

3.1条件变量

3.2条件变量的接口

3.2.1初始化接口(动态初始化)

3.2.2等待接口

3.2.3唤醒接口

3.2.4销毁接口

3.3条件变量的代码演示

3.4条件变量的相关问题

4.生产者消费者模型代码实现


🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴🛴

1.线程安全概念

1.1什么是线程不安全

        在代码运行过程中,多个线程访问共享资源时,一个线程访问的共享资源被其他线程修改,导致线程拿到了错误的数据。这就出现了线程安全问题。

临界区:一次仅允许一个进程使用的共享资源。

临界区资源:每个进程中访问临界资源的那段程序称之为临界区。

进程进入临界区的调度原则
        如果有若干进程请求进入空闲的临界区,一次仅允许一个进程进入,其它线程处于就绪或者等待状态
         任何时候,处于临界区内的进程不可多于一个,若已有进程进入自己的临界区,则其它想进入自己临界区的进程必须等待。
        进行临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

1.2.既然出现了线程不安全,那么如何解决线程不安全问题呢?

        利用互斥同步机制

        互斥:控制线程的访问时序,当多个线程能够同时访问访问临界资源时,有可能会导致线程执行的结果产生二义性。而互斥就是要保证多个线程在访问同一个临界资源,执行临界区代码的时候,控制访问时序。即让一个线程独占临界资源执行完,再让另一个线程独占执行。        

        同步:为了保证多线程对临界资源访问的合理性。即互斥保证线程读写数据不会出错,而同步在互斥的基础上进行优化,增加了访问的合理性,提高了程序运行效率。同步的前提是互斥

2.互斥的实现

2.1互斥锁

每个线程在访问临界资源之前需要拿到互斥锁,互斥锁一次只能被一个线程拿到

互斥锁原理

        互斥锁本身就是0/1计数器,计数器的值只能取0或者取1。1表示当前线程可以获取到互斥锁,从而取访问临界资源.  0表示当前线程不可以获取到互斥锁,从而不能访问互斥资源。这里需要注意,加锁时,需要给所有可能出现不安全的线程加上同一个互斥锁。

2.2互斥锁原子性的保证

        互斥锁的计数器当中的值和计数器内存的值交换,而交换是一条汇编指令就可以完成的,所以保证了原子性。

        加锁的时候,将寄存器的值初始化为0。将寄存器的值和计数器中的值交换,交换后,若寄存器值为1,则加锁成功。若寄存器值为0,说明原来计数器值也是0,即加锁失败。

        解锁的时候,寄存器中的值设置为1。将寄存器和计数器中的值进行交换,计数器的值一定是1,所以保证解锁成功。

2.3互斥锁接口

2.3.1初始化互斥锁

动态初始化互斥锁接口

int pthread_mutex_init( pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);
/*
参数: mutex:传递的互斥锁对象
       attr:  互斥锁属性,一般传递NULL

 返回值:初始化成功,返回0,失败则设置errno,并返回
*/

静态初始化互斥锁的接口

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

#define PTHREAD_MUTEX_INITIALIZER{{ ...}}
//结构体初始化

2.3.2互斥锁加锁接口

阻塞加锁接口

int pthread_mutex_lock(pthread_mutex_t  *mutex);
/*
 参数:  mutex:传递互斥锁对象
 返回值: 加锁成功,返回0,加锁失败就会阻塞等待,直到拿到锁
*/

非阻塞加锁接口

int pthread_mutex_trylock(pthread_mutex_ *mutex);
/*
    参数:传递的互斥锁对象
    返回值:加锁成功,返回0
    注意:需要搭配循环,判断返回值使用

*/

带超时时间加锁接口

int pthread_mutex_timelock(pthread_mutex_t *restrictmutex,const strut timespec *restrict abs_timeout);
/*
  参数:restrictmutex:锁对象
        restrict abs_timeout:结构体,包含时间单位,秒级和纳秒级
    
  注意:
    调用带有超时时间的加锁接口
        空闲的:直接加锁成功返回
        忙碌的:等待时间范围内,阻塞等待,超出时间,锁若还未释放,该函数直接返回
*/

2.3.3解锁接口

int pthread_mutex_unlock(pthread_mutex_t mutex_t  *mutex);
/*
 参数:mutex:要解锁的对象
 返回值:解锁成功返回0
        解锁失败,设置erron并返回
*/

2.3.4 销毁互斥锁接口

int pthread_mutex_destroy(pthread_mutex_t *mutex);
/*

参数: mutex:传递要销毁的互斥锁对象
注意:如果是动态初始化互斥锁的,需要调用销毁接口。如果是静态初始化互斥锁,就不需要销毁

*/

2.4代码验证锁的接口

 #include<stdio.h>
  #include<pthread.h>
  #include<unistd.h>                                                                                                                                     
  
  int glob=1000;
  pthread_mutex_t g_lock;
  
  void* my_thread_start(void* arg){
    while(1){
    
      pthread_mutex_lock(&g_lock);
      
      if(glob<0){
        pthread_mutex_unlock(&g_lock);
        break;
      }
  
      printf("i am %p,glob val is %d\n",pthread_self(),glob);
      glob--;
      pthread_mutex_unlock(&g_lock);
      }
       return NULL;
    }
  
  
  
  int main(){
    
    pthread_mutex_init(&g_lock,NULL);
    pthread_t tid[2];
    int i;
    for( i=0;i<2;i++){
      int ret=pthread_create(&tid[i],NULL,my_thread_start,NULL);
      if(ret<0){
        perror("pthread_create");
        return 0;
      }
    }
    
    //线程等待
    int j;
    for(j=0;j<2;j++){
     pthread_join(tid[j],NULL);
    }
    pthread_mutex_destroy(&g_lock);
    return 0;
  }  

3.同步的实现

3.1条件变量

        上边的互斥已经能够实现基本需求,但是我们现在想象这样一个场景。

        桌子上有一个包,A负责往包里放糖果,B负责从包里拿糖果,现在需求是A放一个糖果,B拿一个糖果,这个时候我们实现就是在线程入口函数中加一个if判断,如果包里没有糖果,只能A拿到锁使用。若B拿到锁,就让它退出。这样在业务实现上确实没有问题。

        那么假设这种场景,包里是空的,B拿到了锁,不满足条件,解锁,B退出。因为是抢占式执行,B又抢到了锁,B又进来,不满足条件,解锁,B退出。这样很浪费CPU资源。

        所以条件变量应运而生,在判断时,条件变量可以将不满足条件的线程强制放入PCB等待队列,也就是将拿糖果的让他强制等待,放了糖果后再唤醒。

3.2条件变量的接口

3.2.1初始化接口(动态初始化)

int pthread_cond_init(pthread cond_t *restrict cond,const pthread_condattr_t *restrict attr);
/*
pthread_cond_t:条件变量类型
参数: cond
    接受一个条件变量的指针或者地址
*/

3.2.2等待接口


int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
/*
参数    cond:条件变量
        mutex:互斥锁

谁调用就将谁放进条件变量的PCB等待队列中
*/

3.2.3唤醒接口

int pthread_cond_broadcast(pthread_cond_t * cond);
//唤醒PCB等待队列当中的所有线程

int pthread_cond_signal(pthread_cond_t * cond);
//唤醒PCB等待队列当中的至少一个线程

/*
参数:cond:条件变量
*/

3.2.4销毁接口

int pthread_cond_destroy(pthread_cond_t* cond);
//将条件变量销毁

3.3条件变量的代码演示

  需求:多个线程往包里放糖,多个线程从包里拿糖,包里一次只能有一个糖。

#include<stdio.h>
  #include<pthread.h>                                                                                                                                    
  #include<unistd.h>
  #define pthread_cout 2
  
  int glob_suga=0;
  
    //定义互斥锁和条件变量
    pthread_mutex_t g_lock;
    pthread_cond_t pushsug;
    pthread_cond_t popsug;
  
  void* push_start_fun(void* arg){
    while(1){
     pthread_mutex_lock(&g_lock);
     while(glob_suga==1){
  
        printf("i am %p,包里有糖果,我去等待了!\n",pthread_self()); 
        pthread_cond_wait(&pushsug,&g_lock);
     }
     printf("i am %p,包里没有糖果,我可以放%d个。\n",pthread_self(),++glob_suga);
  
     pthread_mutex_unlock(&g_lock);
  
     //通知拿糖的人
     pthread_cond_signal(&popsug);
     }
  }

  void* pop_start_fun(void* arg){
    
    while(1){
        pthread_mutex_lock(&g_lock);
       while(glob_suga==0){
        printf("i am %p,包里没有糖果,我去等待了\n",pthread_self());
        pthread_cond_wait(&popsug,&g_lock);
     }
     printf("i am %p,包里有%d个糖果,我拿了\n",pthread_self(),glob_suga);
     glob_suga--;
  
     pthread_mutex_unlock(&g_lock);
  
     //通知放糖的人
     pthread_cond_signal(&pushsug);
    }
  }
  
  
  
  
  int main(){
  
    //初始化互斥锁和条件变量
    pthread_mutex_init(&g_lock,NULL);
    pthread_cond_init(&pushsug,NULL);
    pthread_cond_init(&popsug,NULL);
  
  
    pthread_t push[pthread_cout];
    pthread_t pop[pthread_cout];
    //创建两个线程
    int i;                                                                                                                                               
    for(i=0;i<pthread_cout;i++){
     
      int ret= pthread_create(&push[i],NULL,push_start_fun,NULL);
     if(ret<0){
        perror("pthread_create");
   }
  
    int ret2= pthread_create(&pop[i],NULL,pop_start_fun,NULL);
      if(ret2<0){
       perror("pthread_create");
      }
  
    }
  
    //等待两个线程
    
    for(i=0;i<pthread_cout;i++){
      pthread_join(push[i],NULL);
      pthread_join(pop[i],NULL);
    }
  
  
    //销毁互斥锁
    pthread_mutex_destroy(&g_lock);
    pthread_cond_destroy(&pushsug);
    pthread_cond_destroy(&popsug);
  
  
    return 0;
  }              

3.4条件变量的相关问题

条件变量等待接口第二个参数传递互斥锁是偶然吗?

        因为访问到条件变量,就意味着它已经加锁,若不满足条件,条件变量就将线程放入PCB等待队列,此时 pthread_cond_wait其实是先放进等待队列,然后解锁,否则这个线程带着锁到等待队列,下一个进程 就没法进来了,造成了死锁,程序无法继续执行下去。

        

线程被唤醒后会执行什么代码?

        执行唤醒操作时,会在线程内部进行加锁操作。在抢锁时,抢到了,pthread_cond_wait函数就执行完毕了,函数返回。若没抢到,pthread_cond_wait继续处于抢锁阶段。

4.生产者消费者模型代码实现

         需求:实现一个生产者消费者模型,包含两个生产线程,两个消费线程和一个线程安全的队列,生产线程往队列中放数据,消费线程从队列中读取数据,进行处理。

#include<stdio.h>    
  #include<pthread.h>                                                                                                                                    
  #include<unistd.h>    
  #include<queue>    
      
  using namespace std;    
      
      
  class SafeQueue{    
  private:    
    //队列    
    queue<int> _que;    
    int _capcity;    
    //互斥锁    
    pthread_mutex_t g_lock;    
    //条件变量    
    pthread_cond_t pro;    
    pthread_cond_t con;    
      
  public:                                                                                                                                      
    SafeQueue(){    
      pthread_mutex_init(&g_lock,NULL);                                                       
      pthread_cond_init(&con,NULL);                                                           
      pthread_cond_init(&pro,NULL);    
      _capcity=10;         
    }       
                        
    ~SafeQueue(){                                                             
      pthread_mutex_destroy(&g_lock);    
      pthread_cond_destroy(&con);        
      pthread_cond_destroy(&pro);    
    }           

 void push(int date){
    pthread_mutex_lock(&g_lock);
    while(_que.size()>=10){
      pthread_cond_wait(&pro,&g_lock);
    }
      _que.push(date);
  
      printf("i am product thread:%p,i product %d\n",pthread_self(),date);
      pthread_mutex_unlock(&g_lock);
      pthread_cond_signal(&con);                                                                                                                         
    
   }
  
  
   void pop(int *date){
  
    pthread_mutex_lock(&g_lock);
  
    while(_que.size()<=0){
      //把消费者放到等待队列
      pthread_cond_wait(&con,&g_lock);
    }  
    
      *date=_que.front();
      _que.pop();
   
    printf("i am consume thread:%p,i consume %d\n",pthread_self(),*date);
      
      pthread_mutex_unlock(&g_lock);
      //通知生产者
      pthread_cond_signal(&pro);
   }
  };


 
  #define p_count 2
  
  //消费者线程
  void* my_con(void* arg){
    SafeQueue* sq= (SafeQueue*)arg;
    while(1){
      int date;
      sq->pop(&date);
    }
  
  }
  //生产者线程
  pthread_mutex_t g_data_lock = PTHREAD_MUTEX_INITIALIZER;
  void* my_pro(void* arg){
    SafeQueue* sq= (SafeQueue*)arg;
    int data=0;
    while(1){
      pthread_mutex_lock(&g_data_lock);
      sq->push(data);
      data++;
      pthread_mutex_unlock(&g_data_lock);
    }
  }
  


  int main(){
   
      SafeQueue *sq = new SafeQueue();
      //创建线程
      pthread_t  _con[p_count],_pro[p_count];
      int i;
      for(i=0;i<p_count;i++){
       int ret= pthread_create(&_pro[i],NULL,my_pro,(void*)sq);
       if(ret<0){
         perror("pthread_create");
       }
  
  
       ret= pthread_create(&_con[i],NULL,my_con,(void*)sq);
       if(ret<0){
         perror("pthread_create");
       }
      }
  
      //让主线程等待
      for(i=0;i<p_count;i++){
        pthread_join(_pro[i],NULL);
        pthread_join(_con[i],NULL);
      }
  
  
    delete sq;
    return 0;
  }             

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】线程篇---线程安全&&生产者消费者模型 的相关文章

随机推荐

  • Java中Cookie详解

    最近复习到了Cookie和Session xff0c 这里系统的讲解一下Cookie和Session 在学习这个之前 xff0c 我们需要了解 xff0c 会话的定义 会话是指某一个人打开浏览器 xff0c 访问多个页面 xff0c 然后关
  • 关于ffmpeg,av_read_frame函数返回值小于0的错误

    自己写了一段将视频切成图片的程序 xff0c 在机器上运行 xff0c 发现每次切到10 就结束了 xff0c 截取av read frame返回值 xff0c 发现返回值是AVERROR EOF就是 541478725 xff0c 因为之
  • 在配置log4j.properties中出现问题 ERROR Could not find value for key log4j.appender.Console

    为了测试所有文件是否写好 xff0c 写了一个测试类运行程序 xff0c 报了几行的错误 xff0c 但是还是成功的输出的了数据库的东西 这里是需要在logj4f的配置文件中加上一些东西 xff0c 代码如下 xff1a 解决报错log4j
  • springboot项目启动报错:找不到或无法加载主类【已解决】

    在配 置完一个完整的springboot项目后 xff0c 需要测试是否正确配置 xff0c 出现未加载或未找到加载类的情况 xff0c 可能有以下原因 xff1a 首先需要观察启动类在项目中的位置 xff0c 确保启动类与其他层的包在同一
  • 对于 nested exception is java.net.ConnectException问题【已解决】

    当在springcloud遇到这类问题时 xff0c 需要排查控制器中的路径是否写对 xff0c 由于我报错的项目是一个模拟客户端访问查询业务功能 xff0c 下面是我的源代码 xff08 报错 xff09 xff1a public sta
  • 如何搭建一个基本的Spring项目【Maven】

    相信很多朋友在刚学习Spring时 xff0c 都会存在这个疑问 xff0c 如何正确搭建一个Spring项目 xff0c 以及中间的许多报错无法解决 xff0c 大家可以跟着我一起试一下 xff0c 有问题的朋友评论区见 1 Spring
  • 使用注解开发时需要添加包扫描器【出现问题的可以点进来看看】

    很多第一次接触Spring注解开发的同学 xff0c 在使用注解后容易报错 xff0c 那就很可能是没有在配置文件中加入包扫描器 那么可以往下看 xff0c 以下面这个简单的例子来学习 下面是一个简单的实体类 xff0c 通过Compone
  • 如何使用Autowried,以及与Resource的区别

    在使用注解开发的过程中 xff0c 有个经常会见面的朋友 Autowried xff08 自动装配 xff09 xff0c 刚接触的朋友肯定不能理解自动装配这个词的含义 简单解释一下 xff0c 自动装配将通过已知的类型自动分配对象 xff
  • SpringMVC遇到的有关实体类的报错

    今天在复习SpringMVC数据传参的时候 xff0c 出现了一个非常小的问题 xff0c 主要是为了实验如何将一个对象作为参数传递 xff0c 我创建了两个类 xff0c 但是由于被传递的对象的那个类没有用public 修饰 xff0c
  • app:checkDebugDuplicateClasses错误

    此异常完整表述为 org gradle api tasks TaskExecutionException Execution failed for task 39 app checkDebugDuplicateClasses 39 这类问题
  • LAMP网站架构

    一 LAMP网站架构 1 1 基本定义 LAMP是指一组通常一起使用来运行动态网站或者服务器的自由软件名称首字母缩写 Linux xff0c 操作系统Apache xff0c 网页服务器 MariaDB或MySQL xff0c 数据库管理系
  • pancakeswap薄饼添加流动性后实现永久锁仓

    添加完流动性后 xff0c 永久锁仓就是放弃对资金池的控制 xff0c 就是放弃了对流动性的所有权 xff0c 没有办法撤池子了 现在锁仓分为2种 xff0c 一个是丢黑洞永久锁仓 xff0c 另外一种是短期锁仓 xff0c 锁在智能合约中
  • ubuntu14.04 root用户登录方法

    如果你是刚刚装完ubuntu14 04系统 xff0c 你进去后是以普通用户登录的 xff0c 很多操作并没有权限 xff0c 要想获得全部权限可以以root用户登录 1 先解除root锁定 xff0c 为root用户设置密码 打开终端输入
  • pycharm终端常用指令

    在调试ppddle的时候下载的coco数据集过大 xff0c 一时没有注意不急的如何终止 xff0c 所以转载一个记一下 Terminal快捷键 功能 Tab 自动补全 Ctrl 43 a 光标移动到开始位置 Ctrl 43 e 光标移动到
  • 解决Mac电脑因kotlin插件禁用导致的Android Studio无法打开问题

    解决这个办法需要将Android Studio目录下的disabled plugins txt文档中的org jetbrains kotlin删除即可 文件位置 Users mac Library Application Support G
  • 实现生产者消费者进程(Java)

    目录 前言 一 实验要求 二 步骤 1 主类 2 消费者 3 生产者 4 超市 前言 消费者问题是操作系统中典型的进程同步互斥问题 xff0c xff08 英语 xff1a Producer Consumer problem xff09 x
  • python | Pandas库数据预处理-缺失值篇:info()、isnull()、dropna()、fillna()函数

    相关文章 python Pandas库导入Excel数据 xff08 xlsx格式文件 xff09 函数 xff1a read excel python Pandas库导入csv格式文件函数 xff1a read excel 目录 数据源
  • vue3学习笔记 2023

    vue文件 34 组件 34 是一种封装的思想 把相关业务逻辑的 34 js css html 34 都封装到一起 当需要调用 34 组件 34 的时候 只需要在html中期望的位置插入对应的 34 标签 34 即可 比如封装了一个 34
  • STP详解

    STP STP全称为 生成树协议 xff08 Spanning Tree Protocol xff09 xff0c 是一种网络协议 xff0c 用于在交换机网络中防止网络回路产生 xff0c 保证网络的稳定和可靠性 它通过在网络中选择一条主
  • 【Linux】线程篇---线程安全&&生产者消费者模型

    目录 1 线程安全概念 2 互斥的实现 2 1互斥锁 2 2互斥锁原子性的保证 2 3互斥锁接口 2 3 1初始化互斥锁 2 3 2互斥锁加锁接口 2 3 3解锁接口 2 3 4 销毁互斥锁接口 2 4代码验证锁的接口 3 同步的实现 3