线程同步--生产者消费者模型--单例模式线程池

在这里插入图片描述

文章目录

  • 一.条件变量
    • pthread线程库提供的条件变量操作
  • 二.生产者消费者模型
    • 生产者消费者模型的高效性
    • 基于环形队列实现生产者消费者模型中的数据容器
    • 基于生产者消费者模型实现单例线程池

一.条件变量

  • 条件变量是线程间共享的全局变量,线程间可以通过条件变量进行同步控制
  • 条件变量的使用必须依赖于互斥锁以确保线程安全,线程申请了互斥锁后,可以调用特定函数进入条件变量等待队列(同时释放互斥锁),其他线程则可以通过条件变量在特定的条件下唤醒该线程(唤醒后线程重新获得互斥锁),实现线程同步.
    • 例如一个线程访问队列时,发现队列为空,则它只能等待其它线程将数据添加到队列中,这种情况就需要用到条件变量.
    • 线程同步的概念:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问共享资源,从而有效避免线程饥饿问题(饥饿问题指线程长时间等待资源而无法被调度).
      在这里插入图片描述

pthread线程库提供的条件变量操作

//声明全局互斥锁并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//声明全局条件变量并初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 线程等待条件:
任务线程代码{
	pthread_mutex_lock(&mutex);
	if(条件为假)
		pthread_cond_wait(&cond, &mutex);//等待时会释放互斥锁,等待完后自动加锁
	//访问共享资源....
	pthread_mutex_unlock(&mutex);
}

线程调用pthread_cond_wait等待时,该接口会释放互斥锁,等待结束后自动加锁

  • 控制线程给条件变量发送唤醒信号
控制线程代码{
	if(满足唤醒条件){
		pthread_mutex_lock(&mutex);
		pthread_cond_signal(cond);
		pthread_mutex_unlock(&mutex);
	}
}

唤醒操作加锁是为了避免信号丢失

  • 示例:
#include <iostream>
#include <unistd.h>
#include <pthread.h>

int cnt = 0;
//声明全局互斥锁并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//声明全局条件变量并初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *Count(void * args)
{
    //线程分离,无需主线程等待
    pthread_detach(pthread_self());
    uint64_t number = (uint64_t)args;
    std::cout << "pthread: " << number << " create success" << std::endl;

    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);               
        std::cout << "pthread: " << number << " , cnt: " << cnt++ << std::endl;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    for(uint64_t i = 0; i < 4; i++)
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, Count, (void*)i);
        usleep(1000);
    }
    sleep(3);
    std::cout << "main thread ctrl begin: " << std::endl;

    while(true) 
    {
        sleep(1);
        //唤醒在cond的等待队列中等待的一个线程,默认都是第一个
        pthread_mutex_lock(&mutex);
        pthread_cond_signal(&cond); 
        pthread_mutex_unlock(&mutex);
        //按顺序唤醒在cond的等待队列中的所有线程
        //pthread_cond_broadcast(&cond);
        std::cout << "signal one thread..." << std::endl;
    }

    return 0;
}
  • 线程同步过程图解:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 条件变量和锁的销毁:
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

二.生产者消费者模型

  • 生产者消费者模型是一种多线程并发协作的设计框架,生产者负责生成并发送数据,消费者负责接收并处理数据.
  • 生产者和消费者之间存在一个数据容器作为缓冲区,生产者生产的数据存入容器中,消费者需要的数据从容器中获取,实现了生产者和消费者之间的数据传输解耦
  • 数据容器由互斥锁保护,同一个时刻只能有一个线程访问数据容器,生产者和消费者之间通过条件变量(或信号量)实现同步
  • 对于数据容器的访问,生产者和消费者遵循三个原则:
    • 生产者和生产者之间互斥
    • 消费者和消费者之间互斥
    • 生产者和消费者之间互斥并同步
      在这里插入图片描述

生产者消费者模型的高效性

  • 由于生产者和消费者之间的数据传输解耦,生产者生产完数据之后不用等待消费者处理数据,而是直接将数据存入容器,消费者不需要向生产者请求数据,而是直接从容器里获取数据,因此即便在生产者和消费者的效率不对等且多变的情况下,多个生产者依然可以高效专一地并发生产数据,多个消费者依然可以高效专一地并发处理数据,使得系统整体的并发量得到提高
    在这里插入图片描述

基于环形队列实现生产者消费者模型中的数据容器

  • 环形队列中,消费者访问队列的头指针进行数据出队操作,生产者访问队列的尾指针进行数据入队操作
  • 两把互斥锁分别保证消费者和消费者之间的互斥以及生产者和生产者之间的互斥,两个信号量实现消费者和生产者之间的互斥与同步
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 当环形队列既不为空也不为满时,支持一个生产者和一个消费者并发地进行数据的存取
#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>

//环形队列默认容量
const static int defaultcap = 5;

template<class T>
class RingQueue{
private:
    std::vector<T> ringqueue_;
    int cap_;          //容器的容量

    int c_step_;       // 消费者环形队列指针
    int p_step_;       // 生产者环形队列指针

    sem_t cdata_sem_;  // 消费者的数据资源
    sem_t pspace_sem_; // 生产者的空间资源

    pthread_mutex_t c_mutex_;   //消费者与消费者之间的互斥锁
    pthread_mutex_t p_mutex_;   //生产者与生产者之间的互斥锁
public:
    RingQueue(int cap = defaultcap)
        :ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0)
    {
        //初始化生产者和消费者的信号量-->消费者一开始没有信号量资源,生产者一开始具有最多的空间资源
        sem_init(&cdata_sem_, 0, 0);
        sem_init(&pspace_sem_, 0, cap);

        pthread_mutex_init(&c_mutex_, nullptr);
        pthread_mutex_init(&p_mutex_, nullptr);
    }
    ~RingQueue()
    {
        sem_destroy(&cdata_sem_);
        sem_destroy(&pspace_sem_);

        pthread_mutex_destroy(&c_mutex_);
        pthread_mutex_destroy(&p_mutex_);
    }

    //信号量的资源状态可以区分队列的空和满

    void Push(const T &in) 
    {
        //生产者等待空间资源
        sem_wait(&pspace_sem_);
        pthread_mutex_lock(&p_mutex_);
        ringqueue_[p_step_] = in;
        p_step_++;
        p_step_ %= cap_;
        pthread_mutex_unlock(&p_mutex_);
        //生产完数据后增加消费者的信号量资源
        sem_post(&cdata_sem_);
    }
    void Pop(T *out)      
    {
        //消费者等待数据资源
        sem_wait(&cdata_sem_);
        pthread_mutex_lock(&c_mutex_);
        *out = ringqueue_[c_step_];
        c_step_++;
        c_step_ %= cap_;
        pthread_mutex_unlock(&c_mutex_);
        //消费完数据后增加生产者的信号量资源
        sem_post(&pspace_sem_);
    }
};

基于生产者消费者模型实现单例线程池

在这里插入图片描述

  • 线程池将线程安全的数据容器用容器组织起来的线程封装在一起,系统启动时完成线程的创建,系统关闭时再销毁线程,不仅可以有效提高系统的并发量同时可以避免频繁创建和销毁线程带来的性能损失,组织起来的线程也更方便进行管理和监控.
#pragma once
#include <ctime>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "Mythread.cpp"
#include "sem_cpmodel.cpp"

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

//线程池默认线程数
static const int defalutnum = 5;

template <class T>
class ThreadPool
{
private:
    //用来管理线程的容器
    std::vector<ThreadInfo> threads_;
    //线程安全的环形队列
    RingQueue<T> tasks_;
    //懒汉单例模式静态指针
    static ThreadPool<T> * TPtr;
    static pthread_mutex_t _Slock;
public:
    std::string GetThreadName(pthread_t tid){
        for(const auto &ti : threads_){
            if(ti.tid == tid) return ti.name;
        }
        return "None";
    }
    static ThreadPool<T> * Getinstance(){
        //多套一层判断提高并发效率
        if(TPtr == nullptr){
            //加锁保护静态指针
            pthread_mutex_lock(&_Slock);
            if(TPtr == nullptr){
                std :: cout << "SingleTon Created...." << std ::endl;
                TPtr = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_Slock);
        }
        return TPtr;
    }
private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {}
    ThreadPool(ThreadPool<T>&& TP) = delete;
    ThreadPool(const ThreadPool<T>& TP) = delete;
    ThreadPool<T>& operator=(const ThreadPool<T>& TP) = delete;
public:
    //线程的执行函数
    static void *HandlerTask(void *args){
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true){
            //线程从环形队列中获取任务并执行任务
            T t;
            tp->tasks_.Pop(&t);
            //执行任务代码段-->根据业务需求编写
            std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;
        }
    }

    //创建线程池中的线程
    void Start(){
        int num = threads_.size();
        for (int i = 0; i < num; i++){
            threads_[i].name = "thread-" + std::to_string(i + 1);
            //线程函数参数传递this指针以访问成员变量
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    //将任务存入环形队列中
    void Push(const T &t){
        tasks_.Push(t);
    }
};

//初始化静态指针
template <class T>
ThreadPool<T> * ThreadPool<T>::TPtr = nullptr;
//初始化静态锁
template <class T>
pthread_mutex_t ThreadPool<T>::_Slock = PTHREAD_MUTEX_INITIALIZER;
  • 并发测试:
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

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

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

相关文章

Springboot JSP项目如何以war、jar方式运行

文章目录 一&#xff0c;序二&#xff0c;样例代码1&#xff0c;代码结构2&#xff0c;完整代码备份 三&#xff0c;准备工作1. pom.xml 引入组件2. application.yml 指定jsp配置 四&#xff0c;war方式运行1. 修改pom.xml文件2. mvn执行打包 五&#xff0c;jar方式运行1. 修改…

表白墙网站PHP源码,支持封装成APP

源码介绍 PHP表白墙网站源码&#xff0c;适用于校园内或校区间使用&#xff0c;同时支持封装成APP。告别使用QQ空间的表白墙。 简单安装&#xff0c;只需PHP版本5.6以上即可。 通过上传程序进行安装&#xff0c;并设置账号密码&#xff0c;登录后台后切换模板&#xff0c;适配…

HCIA——21C/S、P2P、peer的选择

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

Java面试题50道

文章目录 1.谈谈你对Spring的理解2.Spring的常用注解有哪些3.Spring中的bean线程安全吗4.Spring中的设计模式有哪些5.Spring事务传播行为有几种6.Spring是怎么解决循环依赖的7.SpringBoot自动配置原理8.SpringBoot配置文件类型以及加载顺序9.SpringCloud的常用组件有哪些10.说一…

Debian11下编译ADAravis和Motor模块的一条龙过程

Debian11编译EPICS ADAravis记录 一年前整理的上面文&#xff0c;这几天重新走了一遍&#xff0c;有些地方会碰到问题&#xff0c;需要补充些环节&#xff0c;motor模块以前和areaDetector一条龙编译时&#xff0c;总是有问题&#xff0c;当时就没尝试了&#xff0c;这几天尝试…

C#串口通讯控制4路继电上位机

C#串口通讯控制4路继电上位机 界面如下 源码如下 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text;//引入空间 using System.Windows.Forms; using System.I…

通过Stable Diffusion生成虚假的遥感影像

简介 这两天玩了一下stable diffusion&#xff0c;是真的好玩&#xff01; 然后我在想遥感有没有相关的生成模型&#xff0c;找了一下&#xff0c;还真找到了&#xff08;https://github.com/xiaoyuan1996/Stable-Diffusion-for-Remote-Sensing-Image-Generation/tree/main&a…

spark 入门教程

一、安装scala环境 官网下载地址 Download | The Scala Programming Language,本次使用版本为sacla2.11.12,将压缩包解压至指定目录&#xff0c;配置好环境变量&#xff0c;控制台验证是否安环境是否可用&#xff1a; 二、添加pom依赖 创建一个maven项目 1、添加scala的sdk依…

详细分析Java中Service报NullPointerException的相关知识(实战Bug)

目录 前言1. 问题所示2. 基本知识3. 原理分析 前言 在Java中&#xff0c;NullPointerException是一种常见的运行时异常&#xff0c;通常发生在尝试访问或操作一个空对象引用&#xff08;null reference&#xff09;时 1. 问题所示 在操作代码的时候&#xff0c;浏览器报服务…

Mysql - 定点型(DECIMAL)的使用详解及练习

目录 &#x1f436;1. 前言&#xff1a; &#x1f436;2. DECIMAL类型简介 &#x1f436;3. Decimal使用实战 &#x1f96a;#结论1&#xff1a;小数位不足会自动补0 &#x1f96a;#结论2&#xff1a;小数位超出会截断 并按四舍五入处理。 &#x1f96a;#结论3&#xff1…

数据结构实验7:查找的应用

目录 一、实验目的 二、实验原理 1. 顺序查找 2. 折半查找 3. 二叉树查找 三、实验内容 实验一 任务 代码 截图 实验2 任务 代码 截图 一、实验目的 1.掌握查找的基本概念&#xff1b; 2.掌握并实现以下查找算法&#xff1a;顺序查找、折半查找、二叉树查找。 …

2.RHCSA启动配置

rht-clearcourse 0 #重置练习环境 rht-setcourse rh134 #切换CSA练习环境 cat /etc/rht #查看当前环境 virt-manager #打开KVM控制台 rht-vmctl start classroom #必做&#xff0c;start all不会包含classroom&#xff0c;需…

【Linux】Ubuntu的gnome切换KDE Plasma

文章目录 安装KDE Plasma桌面环境添加软件源并更新apt安装kubuntu-desktop&#xff08;作者没有成功&#xff09;aptitude安装kubuntu-desktop多次aptitude install&#xff08;特别重要特别重要&#xff09;其他kde软件包 卸载gnome桌面 Ubuntu自带的桌面环境是gnome&#xff…

PSoc62™开发板之rtc时间获取

实验目的 1.使用PSoc62™芯片读取内部rtc时间 2.OLED屏幕显示当前时间戳 实验准备 PSoc62™开发板SSD1306 OLED模块公母头杜邦线 芯片资源 PSoC 6系列MCU时钟系统由以下几部分组成&#xff0c;PSoc62™开发板没有接外部时钟源&#xff0c;所以只能从IMO、ILO、PILO里边配…

EtherNet/IP开发:C++搭建基础模块,EtherNet/IP源代码

这里是CIP资料的协议层级图&#xff0c;讲解协议构造。 ODVA&#xff08;www.ODVA.org&#xff09;成立于1995年&#xff0c;是一个全球性协会&#xff0c;其成员包括世界领先的自动化公司。结合其成员的支持&#xff0c;ODVA的使命是在工业自动化中推进开放、可互操作的信息和…

SQL注入实战:Update注入

一&#xff1a;Update注入原理 有的程序员在写源代码的时候&#xff0c;只是对查询的sql语句的用户输入内容进行了过滤&#xff0c;忽略了update 类型sql语句的用户输入的内容的过滤 二、mysql的Update语句复习 update语句可用来修改表中的数据&#xff0c;简单来说基本的使…

算法每日一题: 分割数组的最大值 | 动归 | 分割数组 | 贪心+二分

Hello&#xff0c;大家好&#xff0c;我是星恒 呜呜呜&#xff0c;今天给大家带来的又是一道经典的动归难题。 题目&#xff1a;leetcode 410给定一个非负整数数组 nums 和一个整数 k &#xff0c;你需要将这个数组分成 k_ 个非空的连续子数组。设计一个算法使得这 k _个子数组…

51单片机电子密码锁Proteus仿真+程序+视频+报告

目录 视频 设计分析 系统结构 仿真图 资料内容 资料下载地址&#xff1a;51单片机电子密码锁Proteus仿真程序视频报告 视频 单片机电子密码锁Proteus仿真程序视频 设计分析 (1)能够从键盘中输入密码&#xff0c;并相应地在显示器上显示‘*’&#xff1b; (2)能够判断密码…

反序列化字符串逃逸(上篇)

首先&#xff0c;必须先明白&#xff0c;这个点并不难&#xff0c;我给大家梳理一遍就会明白。 反序列化字符串逃逸就是序列化过程中逃逸出来字符&#xff0c;是不是很简单&#xff0c;哈哈哈&#xff01; 好了&#xff0c;不闹了&#xff0c;其实&#xff1a; 这里你们只要懂…

【C++ | 数据结构】从哈希的概念 到封装C++STL中的unordered系列容器

文章目录 一、unordered系列容器的底层结构 - 哈希1. 哈希概念2. 哈希冲突 二、解决哈希冲突方法一&#xff1a;合理设计哈希函数&#x1f6a9;哈希函数设计原则&#x1f6a9;常见哈希函数 方法二&#xff1a;开闭散列&#x1f6a9;闭散列线性探测法&#xff08;实现&#xff0…