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

news2024/9/24 13:35:27

在这里插入图片描述

文章目录

  • 一.条件变量
    • 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;
  • 并发测试:
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[已解决]前端使用el-upload,后端使用文件上传阿里云报错:异常信息:java.lang.NullPointerException: null

前端使用el-upload&#xff0c;后端使用文件上传阿里云报错&#xff1a; 报错原因&#xff1a;前端image参数未传进去 解决方法&#xff1a;在el-upload添加属性 name"image" 文件传进去了&#xff01;

Redis实战之-分布式锁-redission

一、分布式锁-redission功能介绍 基于setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如HashTable这样的代码中&#xff0c;他的方法都…

2.4 网络层03

2.4 网络层03 2.4.7 路由表 1、什么是路由&#xff1f; 路由就是报文从源端到目的端的路径。当报文从路由器到目的网段有多条路由可达时&#xff0c;路由器可以根据路由表中最佳路由进行转发。 2、什么是路由表&#xff1f; 在计算机网络中&#xff0c;路由表&#xff08…

关于java中的Super详解

关于java中的Super详解 我们在上一篇文章中了解到了面向对象三大基本特征&#xff0c;继承&#xff0c;我们本篇文章中来了解一下Super&#x1f600;。 一、Super和this调用属性 this&#xff1a;当前类中使用。super&#xff1a;父类使用。 我们直接用代码来说明一下。 1…

C#调用C++ dll异常排查

基本情况 最近在做的一款程序&#xff0c;长时间运行总会出现莫名的问题。有时是自动关闭&#xff0c;有时程序报错&#xff0c;有时调用的dll异常…… 提出假设——dll内存泄漏 由于开始与C组合作时&#xff0c;使用其提供的dll出现过数据读写时异常&#xff08;内存操作异常…

Python 面向对象绘图(Matplotlib篇-16)

Python 面向对象绘图(Matplotlib篇-16)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

[AutoSar]BSW_OS 05 Autosar OS_Alarms

目录 关键词平台说明一、Timer1.1 配置 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &#xff0c;芯片厂商TI 英飞凌编程语言C&#xff0c;C编译器HighTec (GCC) >>>>>回到总目录<<<<<< 一…

kafka参数配置参考和优化建议 —— 筑梦之路

对于Kafka的优化&#xff0c;可以从以下几个方面进行思考和优化&#xff1a; 硬件优化&#xff1a;使用高性能的硬件设备&#xff0c;包括高速磁盘、大内存和高性能网络设备&#xff0c;以提高Kafka集群的整体性能。 配置优化&#xff1a;调整Kafka的配置参数&#xff0c;包括…

【GAMES101】Lecture 08 图形管线(实时渲染管线)与纹理映射

目录 图形管线 纹理映射 图形管线 给我一个三维模型&#xff0c;给我一个光照条件&#xff0c;我就能够得出渲染的结果&#xff0c;这些东西合起来就是Graphics Pipeline&#xff0c;图形管线&#xff0c;闫神愿称之为实时渲染管线&#xff0c;那下面这个流程图就是这个渲染…

MySQL缓冲池(Buffer Pool)深入解析:原理、组成及其在数据操作中的核心作用

在关系型数据库管理系统&#xff08;RDBMS&#xff09;中&#xff0c;性能优化一直是数据库管理员和开发者关注的焦点。作为最流行的开源RDBMS之一&#xff0c;MySQL提供了多种优化手段&#xff0c;其中InnoDB存储引擎的缓冲池&#xff08;Buffer Pool&#xff09;是最为关键的…

网络安全需要对网络风险有独特的理解

迷失在翻译中&#xff1a;网络风险解释的脱节现实 在古印度的一个经典故事中&#xff0c;几个蒙住眼睛的人接近一头大象&#xff0c;每个人检查不同的部位。有人触摸树干&#xff0c;认为它像一条蛇。另一个摸到了一条腿&#xff0c;认为它是一棵树。还有一个拿着象牙的人&…

C++大学教程(第九版)5.23星号组成的菱形图案 5.24修改星号组成的菱形图案

5.23题目 (星号组成的菱形图案)编写一个程序&#xff0c;打印以下的菱形图案。要求使用打印一个星号(*)、空格或者一个换行符的输出语句。尽量多用循环(使用嵌套 for 语句)&#xff0c;同时尽量减少输出语句的使用次数。 代码 #include <bits/stdc.h> using namespac…

Unity中URP下的SimpleLit片元着色器

文章目录 前言一、SimpleLit片元着色器大体框架1、传入 和 返回2、GPU实例化部分3、准备 BlinnPhong 光照模型计算需要的 SurfaceData4、准备 BlinnPhong 光照模型计算需要的 InputData5、进行 BlinnPhong 的计算、雾效颜色混合及透明度计算 二、准备SurfaceData1、SurfaceData…

消除游戏(寒假每日一题+模拟、优化)

题目 在一个字符串 S 中&#xff0c;如果 SiSi−1 且 Si≠Si1&#xff0c;则称 Si和 Si1 为边缘字符。 如果 Si≠Si−1 且 SiSi1&#xff0c;则 Si−1 和 Si 也称为边缘字符。 其它的字符都不是边缘字符。 对于一个给定的串 S&#xff0c;一次操作可以一次性删除该串中的所…

大数据开发之Hadoop(完整版+练习)

第 1 章&#xff1a;Hadoop概述 1.1 Hadoop是什么 1、Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 2、主要解决&#xff0c;海量数据的存储和海量数据的分析计算问题。 3、Hadoop通常是指一个更广泛的概念-Hadoop生态圈 1.2 Hadoop优势&#xff08;4高&#xf…

Centos7 如何设置开机启动某个程序

以设置自动启动sentinel-dashboard作为案例 要在CentOS 7上设置开机启动一个Java程序&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 进入应用程序的目录 cd /usr/localvim sentinel-dashboard.sh 2. 在sentinel-dashboard.sh 文件中 输入启动脚本 nohup java -D…

Redis高级系列-缓存双写一致性

Redis高级系列-缓存双写一致性 文章目录 Redis高级系列-缓存双写一致性1. 什么叫做缓存双写一致性&#xff1f;2. 缓存双写一致性有那些解决方案&#xff1f;2.1 Cache Aside Pattern(旁路缓存模式)延迟双删重试删除binlog订阅异步删除 2.2 Read Through/Write Through(读写穿透…

爬虫之牛刀小试(八):爬取微博评论

今天爬取的是微博评论。 可以发现其特点是下一页评论的max_id在上一页中。 于是代码如下&#xff1a; import requests import json import re import time headers {User-Agent: ,"Cookie": "","Referer": "https://m.weibo.cn/detail/4…

Elasticsearch 数据类型相关总结:快速参考指南【记录】

在Elasticsearch中&#xff0c;有多种数据类型可用于定义字段。 在开始了解数据类型之前&#xff0c;首先要知道&#xff0c;在Elasticsearch中&#xff0c;分词处理主要针对文本字段&#xff0c;而对于其他类型字段&#xff08;如数值、日期、布尔等&#xff09;&#xff0c;通…

绑定class,条件渲染,列表过滤,列表排序

目录​​​​​​​ 绑定class 条件渲染 列表过滤 列表排序 绑定class <div class"normal" :class"mood" click"changename">111{{name}}</div><div class"normal" :class"arr">111{{name}}</div…