【linux】线程同步和生产消费者模型

线程同步

当我们多线程访问同一个临界资源时,会造成并发访问一个临界资源,使得临界资源数据不安全,我们引入了锁的概念,解决了临界资源访问不安全的情况,对于线程而言竞争锁的能力有强有弱,对于之前就抢到锁的线程,当他释放锁后,由于不用做什么准备工作,他竞争锁的能力很强,导致这个线程反复的争夺锁,来访问临界资源,导致其他线程处于饥饿状态
同步:同步问题是保证数据安全的情况下,让我们的线程具有一定的顺序性

解决方案

条件变量的引入
当多线程来访问临界资源时,首先不会让他去访问临界资源,而是将这个线程放入条件变量维护的队列中去,等待临界资源就绪,举个例子,一个幼儿园里面,到了饭点,而小朋友们是每一个线程,而饭就是临界资源,每次只能有一个孩子在餐厅里面打饭,这就是锁,每个孩子跑步速度不一样,竞争锁的能力不一样,为了避免一个孩子一直在餐厅不走,一直吃饭,幼儿园老师做了这个规定,每个小朋友吃饭必须去排队,刚吃完的孩子还想吃的话,必须排队在队的后面。而条件变量维护的队列类比与排队,每个线程访问完临界资源之后必须在条件变量维护的队列后面排队
在这里插入图片描述
在这里插入图片描述

条件变量接口介绍

1、主要应用函数:

pthread_cond_init()函数
功能:初始化一个条件变量
pthread_cond_wait()函数
功能:阻塞等待一个条件变量
pthread_cond_signal()函数
功能:唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast()函数
功能:唤醒全部阻塞在条件变量上的线程
pthread_cond_destroy()函数
功能:销毁一个条件变量

函数分析

1.初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 

参2:attr表条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2.阻塞等待一个条件变量

 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 

函数作用:

阻塞等待条件变量cond(参1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

3.唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond);

4.唤醒全部阻塞在条件变量上的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

5.销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

代码实现线程同步

1.makefile编写

mycond:mycond.cc
	g++ -o mycond mycond.cc -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f mycond

2.mycond.cc

#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

int cnt=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void*fun(void*args)
{
  pthread_detach(pthread_self());
  uint64_t num=(uint64_t)args;
  cout<<"pthread:"<<num<<"create success"<<endl;
  while(1)
  { pthread_mutex_lock(&mutex);
   cout<<"pthread:"<<num<<",cnt:"<<cnt++<<endl;
    pthread_mutex_unlock(&mutex);
  }

}
int main()
{

for( uint64_t i=0;i<5;i++)
{
   pthread_t tid;
   pthread_create(&tid,nullptr,fun,(void*)i);
}
while(1)
{


    
}
}

代码解释:
初始化一个锁,一个条件变量,临界资源cnt,每个线程在自己要执行的fun函数内,需要访问临界资源cnt,对cnt++,在主函数中创建5个线程,为了防止主线程退出,所有线程都退出,所以让主线程死循环,在每个fun函数里面实现线程分离,不需要主线程来等待回收其他线程,让操作系统自己回收

在这里插入图片描述
由于线程2竞争锁的能力强,每次都是线程2来访问临界资源。
为了解决一个线程竞争锁的能力强,使用线程同步,先实现代码在来解释

#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

int cnt=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void*fun(void*args)
{
  pthread_detach(pthread_self());
  uint64_t num=(uint64_t)args;
  cout<<"pthread:"<<num<<"create success"<<endl;
  while(1)
  { pthread_mutex_lock(&mutex);
   pthread_cond_wait(&cond,&mutex);//新增行
   cout<<"pthread:"<<num<<",cnt:"<<cnt++<<endl;
    pthread_mutex_unlock(&mutex);
    
  }

}
int main()
{

for( uint64_t i=0;i<5;i++)
{
   pthread_t tid;
   pthread_create(&tid,nullptr,fun,(void*)i);
}
while(1)
{
sleep(1);//新增行
pthread_cond_signal(&cond);//新增行
cout<<"signal one thread..."<<endl;    //新增行
}
}

pthread_cond_wait函数可以将刚申请锁的线程让其加入队列,让其休眠,该函数还会让对应的线程释放锁,这样一轮下来,所有想访问临界资源的线程都会出现在条件变量维护的队列中,等待唤醒,一次唤醒一个,让他们去访问临界资源,在主线程中来唤醒在维护条件变量对应的队列中其他线程,去访问临界资源,sleep作用防止打印太快看不到效果。
在这里插入图片描述
五个线程按照顺序去访问临界资源


cp问题(生产消费者模型)

在这里插入图片描述
此时有三种关系:
生产者和生产者:互斥
消费者和生产者:互斥,同步(一个放,一个拿,肯定要保证顺序问题)
消费者和消费者:互斥
3种关系:生产者和生产者,消费者和消费者,生产者和消费者
2种角色:生产者和消费者
1个交易场所:特定结构的内存空间

特点:支持忙闲不均(对比冯诺依曼结构)
生产和消费进行解耦

代码实现生产消费者模型

makefile实现

cp:cp.cc
  g++ -o cp cp.cc -std=c++11 -lpthread
.PHONY:clean
clean:
  rm -f cp

cp.cc(未完成)


#include<iostream>
#include<pthread.h>
#include"blockqueue.hpp"
#include<unistd.h>

using namespace std;
void*consumer(void*args)
{
Blockqueue<int>*cq=static_cast<Blockqueue<int>*>(args);
while(1)
{
//消费数据,将队列中的数据pop
int data=cq->pop();
cout<<"消费了一个数据:"<<data<<endl;
}
}
void* productor(void*args)
{
 int data=0;
Blockqueue<int>*pq=static_cast<Blockqueue<int>*>(args);
while(1)
{ sleep(1);

 //生产数据放到队列中去
  pq->push(data);
  cout<<"生产了一个数据:"<< data++ <<endl;
}

}




int main()
{
pthread_t p,c;
Blockqueue<int>*st=new Blockqueue<int>();


 pthread_create(&p,nullptr,productor,st);
 pthread_create(&c,nullptr,consumer,st);


pthread_join(p,nullptr);
pthread_join(c,nullptr);//线程等待回收
delete st;
return 0;

}

在这里插入图片描述
主线程创建两个线程,一个是生产者,一个是消费者,生产者将数据插入在阻塞队列中,消费者将数据取出阻塞队列中的数据,阻塞队列相当于临界资源,主线程中new一个阻塞队列对象作为共享的资源,在各个线程要执行的函数里面,pthread_create最后一个参数可以是资源的起始地址,我们可以传阻塞队列类对象过去,让生产者线程和消费者线程都可以访问到
blockqueue.hpp

#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;

template<class T>
class Blockqueue
{

static const int defalutnum=5;
public:
Blockqueue(int maxcap=defalutnum)
:maxcap_(maxcap)
 {
  pthread_mutex_init(&mutex_,nullptr);
  pthread_cond_init(&p_cond_,nullptr);
    pthread_cond_init(&c_cond_,nullptr);
 }
T pop()
{
  T out=q_.front();
  q_.pop();
  return out;
}
void push(const T&in)
{
q_.push(in);
}
~Blockqueue()
 {
  pthread_mutex_destroy(&mutex_);
  pthread_cond_destroy(& p_cond_);
    pthread_cond_destroy(& c_cond_);
    
 }
private:
queue<T>q_;
int maxcap_;
pthread_mutex_t mutex_;
pthread_cond_t p_cond_;
pthread_cond_t c_cond_;

};

由于该阻塞队列是两者共享的,临界资源,防止并发访问,要实现安全保护,所以要使用锁,因为生产者和消费者都会对临界资源访问,两者对锁的竞争能力不一样,可能会导致饥饿问题,使用线程同步,所以要用条件变量
maxcap为队列最大可以放几个值,这是我们规定的,为了解决忙而不均,同步问题,达到最大,就让生产者线程休眠,如果阻塞队列的个数为0,就让消费者线程休眠,所以这里需要两个条件变量分别维护。


现在要解决的是临界资源的保护,保证多线程并发安全,使用锁,还要就是两个线程竞争锁的能力不同,可能导致另一个线程饥饿问题,使用线程同步
在这里插入图片描述

blockqueue.hpp

#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;

template<class T>
class Blockqueue
{

static const int defalutnum=5;
public:
Blockqueue(int maxcap=defalutnum)
:maxcap_(maxcap)
 {
  pthread_mutex_init(&mutex_,nullptr);
  pthread_cond_init(&p_cond_,nullptr);
   pthread_cond_init(&c_cond_,nullptr);
   max_water=(maxcap*2)/3;
   min_water=maxcap/3;
 }
T pop()
{ pthread_mutex_lock(&mutex_);
 while(q_.size()==0)
    {
    pthread_cond_wait(&c_cond_,&mutex_);
    }
  T out=q_.front();
  q_.pop();
  if(q_.size()<min_water)
  pthread_cond_signal(&p_cond_);
  pthread_mutex_unlock(&mutex_);
  return out;
}
void push(const T&in)
{
pthread_mutex_lock(&mutex_);
while(q_.size()==maxcap_)
{pthread_cond_wait(&p_cond_,&mutex_);}
q_.push(in);
if(q_.size()>max_water)
pthread_cond_signal(&c_cond_);
pthread_mutex_unlock(&mutex_);
}
~Blockqueue()
 {
  pthread_mutex_destroy(&mutex_);
  pthread_cond_destroy(&p_cond_);
   pthread_cond_destroy(&c_cond_);

    
 }
private:
queue<T>q_;
int maxcap_;
pthread_mutex_t mutex_;
pthread_cond_t p_cond_;
pthread_cond_t c_cond_;
int max_water;
int min_water;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/676804.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图形学初识--颜色混合

文章目录 前言正文为什么要有颜色混合&#xff1f;颜色混合常见实现方式&#xff1f;上述颜色混合注意点 结尾&#xff1a;喜欢的小伙伴点点关注赞哦! 前言 本章节补充一下颜色混合的内容&#xff0c;主要包含&#xff1a;为什么要有颜色混合&#xff1f;颜色混合常实现方式&a…

外星人Alienware x17R1 原厂Windows11系统

装后恢复到您开箱的体验界面&#xff0c;包括所有原机所有驱动AWCC、Mydell、office、mcafee等所有预装软件。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http://w…

全文检索-ElasticSearch

1.基本概念 1.Index索引 动词&#xff1a;相当于MySQL中的insert&#xff1b; 名词&#xff1a;相当于MySQL中的DataBase&#xff1b; 2.Type&#xff08;类型&#xff09; 在Index&#xff08;索引&#xff09;中&#xff0c;可以定义一个或多个类型 类似于MySQL中的Tab…

Docker大学生看了都会系列(二、2.1Mac通过Homebrew安装Docker)

系列文章目录 第一章 Docker介绍 第二章 2.1 Mac通过Homebrew安装Docker 第二章 2.2 CentOS安装Docker 文章目录 前言Mac通过Homebrew安装本机环境系统要求terminal命令安装查看安装信息配置阿里云镜像加速登陆阿里云配置加速地址其他国内加速地址 总结 前言 在上一章了解了Do…

步进电机双闭环细分控制(matlab仿真)内含课设等参考文件

1.1 步进电机工作原理 步进电机是一种用电脉冲进行控制&#xff0c;将电脉冲信号转换成相位移的电机&#xff0c;其机械位移和转速分别与输入电机绕组的脉冲个数和脉冲频率成正比,每一个脉冲信号可使步进电机旋转一个固定的角度。脉冲的数量决定了旋转的总角度&#xff0c;脉…

FebHost:什么是国别国外域名以及用途?

在数字时代的浪潮中&#xff0c;互联网如同一张无形的网&#xff0c;将全球的人们紧密相连。尽管世界因此变得“更小”&#xff0c;但在网络上展示个人或企业身份时&#xff0c;标明国家归属或地理位置依然显得格外重要。今天&#xff0c;我们将深入探讨国别域名的重要性、使用…

查看所用数据库的版本

通过查询 SELECT VERSION();

java面试题及答案2024,java2024最新面试题及答案(之二)

四、反射 57. 什么是反射&#xff1f; 反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力 Java反射&#xff1a; 在Java运行时环境中&#xff0c;对于任意一个类&#xff0c;能否知道这个类有哪些属性和方法&#xff1f;对于任意一个对象&#xff0c;能否调…

顺序表的讲解与实现

顺序表的讲解与实现 一、顺序表的概念及结构二、顺序表分类顺序表和数组的区别顺序表分类静态顺序表动态顺序表 三、动态顺序表的实现(使用VS2022)1.初始化、销毁、打印内容2.检查扩容3.尾部插入、尾部删除、头部插入、头部删除尾部插入尾部删除头部插入头部删除 4.指定插入、指…

Go语言 几种常见的IO模型用法 和 netpoll与原生GoNet对比

【go基础】16.I/O模型与网络轮询器netpoller_go中的多路io复用模型-CSDN博客 字节开源的netPoll多路复用器源码解析-CSDN博客 一、几种常见的IO模型 1. 阻塞I/O (1) 解释&#xff1a; 用户调用如accept、read等系统调用&#xff0c;向内核发起I/O请求后&#xff0c;应用程序…

装机必备——鲁大师安装教程

装机必备——鲁大师安装教程 软件下载 软件名称&#xff1a;鲁大师 软件语言&#xff1a;简体中文 软件大小&#xff1a;144.75M系统要求&#xff1a;Windows7或更高&#xff0c; 32/64位操作系统 硬件要求&#xff1a;CPU2GHz &#xff0c;RAM2G或更高 下载通道①迅雷云盘丨…

探究 Meme 的金融与社交属性

原文标题&#xff1a;《A Social and Financial Study of Memecoins》撰文&#xff1a;Andrew Hong编译&#xff1a;Chris&#xff0c;Techub News 每一个市场周期都伴随着 Meme 代币的出现。一群人围绕着某个 Meme 集结起来&#xff0c;暂时抬高了某个资产的价格&#xff08;从…

YOLOv5白皮书-第Y5周:yolo.py文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、环境 语言&#xff1a;Python3、Pytorch开发环境电脑系统&#xff1a;Windows 10语言环境&#xff1a;Python 3.9.2编译器&#xff1a;VS Code显卡&#…

LeetCode刷题之HOT100之组合总和

2024/6/3 周一&#xff0c;工作日的第一天。昨晚梦到被导师说去实验室不积极哈哈哈&#xff0c;风扇开到二级&#xff0c;早上被吹醒。买的书马上快要到了。上午刚来准备刷题&#xff0c;结果去搞了一下数据库sql&#xff0c;做的差不多了&#xff0c;还差点格式转换就差不多出…

暴力数据结构之排序大杂烩

1. 冒泡排序&#xff1a;O(N^2) 逻辑解析&#xff1a; 冒泡排序并没有什么实际意义&#xff0c;但是有教学意义&#xff0c;相信大部分小白在学习的初期第一个接触的排序就是冒泡排序。那么接下来我们了解一下他的底层逻辑&#xff1a; 冒泡排序顾名思义就是将最大&#xff08…

力扣 101. 对称二叉树

给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool check(struct TreeNode* L,struct TreeNode* R){if(!L&…

匠心独运,B 端系统 UI 演绎华章之美

匠心独运&#xff0c;B 端系统 UI 演绎华章之美

kettle从入门到精通 第六十四课 ETL之kettle kettle中执行SQL脚本步骤,使用需当心

1、群里有不定时会有同学反馈执行SQL脚本步骤使用有问题&#xff0c;那么咱们今天一起来学习下该步骤。trans中的执行SQL脚本有两方面功能&#xff0c;使用时需小心&#xff0c;不然很容易踩坑。 官方定义&#xff1a; 翻译&#xff1a; 您可以使用此步骤执行 SQL 脚本&#…

kafka集群内外网分流方案——筑梦之路

前言 在现代分布式系统架构中&#xff0c;Kafka作为一款高性能的消息队列系统&#xff0c;广泛应用于大数据处理、实时流处理以及微服务间的异步通信场景。特别是往往企业级应用中&#xff0c;业务网段和内网通信网段不是同一个网段&#xff0c;内网的机器想要访问业务数据只能…

考研人注意了:六月份保底进度+复习规划!

六月开始准备考研确实有点晚了&#xff0c;因为大家基本上上都是3月份开始的 开始的比别人晚&#xff0c;学习的时间就会比较紧张&#xff0c;但是不要怕&#xff0c;考研并不是比谁学的时间长&#xff0c;而是比谁学的效果好&#xff0c;就算是六月份开始&#xff0c;只要学习…