【Linux多线程】一个基于环形队列实现的案例

news2024/11/19 18:43:20

00

环形队列

  • 前言
  • sem信号量
  • 程序代码
    • pthread.hpp
    • 代码说明
    • RingQueue.cc
    • 代码说明
    • Makefile
  • 运行

前言

sem信号量

sem_t 是信号量(Semaphore)的数据类型,用于在多线程或多进程环境中实现线程同步和资源控制。

信号量是一个计数器,用来控制对共享资源的访问。它主要有两个基本操作:wait 和 post,也被称为 P 操作和 V 操作。wait 操作用于申请资源并等待资源可用,若资源不可用,则线程或进程进入阻塞状态。post 操作用于释放资源,使得其他线程或进程可以继续执行。

在 C/C++ 中,信号量的相关接口主要包括以下函数:

sem_init(sem_t *sem, int pshared, unsigned int value):初始化信号量。sem 是指向信号量变量的指针;pshared 表示信号量是否在多个进程之间共享,一般在线程间使用时设置为 0;value 是信号量的初始值。

sem_wait(sem_t *sem):执行 P 操作。如果信号量的值大于 0,将信号量的值减 1,继续执行。如果信号量的值为 0,则线程或进程进入阻塞状态,直到有其他线程或进程执行了 sem_post 操作来释放资源。

sem_trywait(sem_t *sem):非阻塞的 P 操作。与 sem_wait 类似,但如果信号量的值为 0,则不会阻塞线程或进程,而是直接返回,并返回错误码为 EAGAIN。

sem_timedwait(sem_t *sem, const struct timespec *abs_timeout):超时 P 操作。与 sem_wait 类似,但如果信号量的值为 0,则线程或进程将等待指定的超时时间,超过时间后仍未获得资源,则返回错误码为 ETIMEDOUT。

sem_post(sem_t *sem):执行 V 操作。将信号量的值加 1,并通知等待在该信号量上的其他线程或进程可以继续执行。

sem_getvalue(sem_t *sem, int *sval):获取信号量的当前值,并将其保存在 sval 中。

sem_destroy(sem_t *sem):销毁信号量,释放相关资源。

使用信号量可以实现对共享资源的有序访问和线程同步,保证多个线程或进程之间的正确协作,避免资源竞争和死锁等问题。

程序代码

pthread.hpp

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>
const int gCap=5;
template<class T>
class RingQueue
{

    public:
    RingQueue(int cap=gCap):_ringqueue(cap),_Pindex(0),_Cindex(0)
    {
        
         // 生产
       sem_init(&_roomSem, 0, _ringqueue.size());

        // 消费
        sem_init(&_dataSem, 0, 0);

        pthread_mutex_init(&_pmutex ,nullptr);
        pthread_mutex_init(&_cmutex,nullptr);
    }
    ~RingQueue()
    {
        sem_destroy(&_roomSem);
        sem_destroy(&_dataSem);

        pthread_mutex_destroy(&_pmutex);
        pthread_mutex_destroy(&_cmutex);
    }


    void push(const T &in)
    {
        sem_wait(&_roomSem);//挂起等待机制 确保无法被多次申请
        //加锁 临界区
        pthread_mutex_lock(&_pmutex);
        _ringqueue[_Pindex]=in;
        _Pindex++;
        _Pindex%=_ringqueue.size();//更新下标 确保环形队列
        pthread_mutex_unlock(&_pmutex);
        sem_post(&_dataSem);//唤醒该线程下面等待的其他线程执行
    }

    T pop()
    {
        sem_wait(&_dataSem);
        pthread_mutex_lock(&_cmutex);

        T temp = _ringqueue[_Cindex];
        _Cindex++;
        _Cindex %= _ringqueue.size();// 更新下标,保证环形特征


        pthread_mutex_unlock(&_cmutex);     
        sem_post(&_roomSem);

        return temp;
    }
    private:
    std::vector <T> _ringqueue;//环形队列
    sem_t _roomSem;//衡量空间计数器,生产者
    sem_t _dataSem;//衡量数据计数器,消费者
    uint32_t _Pindex;//当前生产者写入的位置,如果是多线程,那么要作为临界资源处理
    uint32_t _Cindex;//当前消费者读取的位置
    pthread_mutex_t _pmutex;//消费者线程
    pthread_mutex_t _cmutex;//生产者线程
};

代码说明

这是一个环形队列(RingQueue)的C++实现。环形队列是一种数据结构,可以用于在生产者和消费者之间传递数据,且当队列满时会从队列的起始位置开始覆盖数据。以下是对代码的详细解释:

成员变量:

_ringqueue:一个std::vector类型的容器,用于存储队列数据。
_roomSem和_dataSem:两个信号量,用于生产者和消费者之间的同步。其中,_roomSem信号量用于保护队列剩余空间的计数,_dataSem信号量用于保护队列中数据项的计数。
_Pindex和_Cindex:两个uint32_t类型的索引,分别表示生产者和消费者在队列中的位置。
_pmutex和_cmutex:两个pthread_mutex_t类型的互斥量,用于保护_Pindex和_Cindex。

构造函数和析构函数:

RingQueue(int cap=gCap):构造函数,接受一个可选参数表示队列的容量,初始化环形队列、信号量和互斥量。
~RingQueue():析构函数,销毁环形队列中的信号量和互斥量。
成员函数:

push(const T &in):生产者调用的函数,将一个元素添加到环形队列中。首先,它等待_roomSem信号量,以确保队列中有空间可用。然后,它获取_pmutex互斥量来保护生产者索引,将元素添加到队列,并更新生产者索引。最后,释放互斥量并发布_dataSem信号量,表示队列中的数据项数量增加了。
pop():消费者调用的函数,从环形队列中取出一个元素。首先,它等待_dataSem信号量,以确保队列中有数据可用。然后,它获取_cmutex互斥量来保护消费者索引,从队列中取出元素,并更新消费者索引。最后,释放互斥量并发布_roomSem信号量,表示队列中的可用空间增加了。

这个类实现了生产者-消费者问题的一种解决方案,其中生产者和消费者可以是在不同线程中运行的函数或方法。这种模式常用于多线程编程,例如,生产者可以是生成数据的线程,而消费者可以是处理数据的线程。

RingQueue.cc

#include"pthread.hpp"
#include <ctime>
#include <unistd.h>
using namespace std;

void *productor(void *args)
{
    RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
    while(true)
    {
        int data = rand()%10;
        rqp->push(data);
        cout << "pthread[" << pthread_self() << "]" << " 生产了一个数据: " << data << endl;
        sleep(1);
    }
}
void *consumer(void *args)
{
    RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
    while(true)
    {
        //sleep(10);
        int data = rqp->pop();
        cout << "pthread[" << pthread_self() << "]" << " 消费了一个数据: " << data << endl;
    }
}

int main()
{

   srand((unsigned long)time(nullptr)^getpid());

    RingQueue<int> rq;

    pthread_t c1,c2,c3, p1,p2,p3;
    pthread_create(&p1, nullptr, productor, &rq);
    pthread_create(&p2, nullptr, productor, &rq);
    pthread_create(&p3, nullptr, productor, &rq);
    pthread_create(&c1, nullptr, consumer, &rq);
    pthread_create(&c2, nullptr, consumer, &rq);
    pthread_create(&c3, nullptr, consumer, &rq);


    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    pthread_join(p3, nullptr);

    return 0;


    return 0;
}

代码说明

这是一个C++程序,使用前面定义的RingQueue类实现一个多线程的生产者-消费者模型。以下是对代码的详细解释:

函数:

productor(void *args):这是生产者线程的入口函数。它从传入的参数中获取一个RingQueue对象的指针,然后在一个无限循环中不断向队列中添加随机数。每次添加一个数字后,它会打印一条信息,并休眠一秒。
consumer(void *args):这是消费者线程的入口函数。它从传入的参数中获取一个RingQueue对象的指针,然后在一个无限循环中不断从队列中取出数字。每次取出一个数字后,它会打印一条信息。

主函数:

首先,它使用当前时间和进程ID来初始化随机数生成器。
创建一个RingQueue实例。
创建三个生产者线程和三个消费者线程。每个线程的入口函数分别是productor和consumer,并且都传入了RingQueue的地址作为参数。
使用pthread_join函数等待所有的线程结束(尽管在这个程序中,线程并不会自行结束)。

这个程序展示了如何在多线程环境中使用RingQueue实现生产者-消费者模型。生产者线程生成数据并添加到队列,消费者线程从队列中取出数据并处理。由于RingQueue类内部使用了信号量和互斥量来同步线程,所以这个程序可以在多线程环境中安全地使用。

Makefile

CC=g++
FLAGS=-std=c++11
LD=-lpthread
bin=test
src=RingQueue.cc

$(bin):$(src)
	$(CC) -o $@ $^ $(LD) $(FLAGS)
.PHONY:clean
	rm -f $(bin)

运行

00

00

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

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

相关文章

小红书2023“家生活”趋势白皮书

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 近年来&#xff0c;年轻人与家的关系愈发紧密。 在小红书上&#xff0c;我们观察到了家居家装内容的蓬勃生长&#xff0c;3 年来相关内容的笔记规模增长了6倍&#xff0c;相关品类的搜索量增加的 3.…

10个学习JavaScript的理由

当你决心学习一门语言的时候&#xff0c;很难选择到底应该学习哪一门&#xff0c;常用的语言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-C和SQL&#xff0c;但这并不是完整的列表。 文章目录 一、命名&#xff1a;JavaScript的由来 二、学习JavaSc…

SAP MDG —— MDG on S/4HANA 2023 创新汇总(Central Governance)

文章目录 MDG 基于SAP S/4HANA 2023的创新集中治理MDG通用架构相关基于已激活的数据刷新快照和未激活数据在文件上载时&#xff0c;将导入的源文件附到生成的变更申请附件中文件上载中新的层次结构更新模式文件下载中使用字段描述替换字段技术名称 MDG财务主数据相关当在MDG搜索…

Element的el-select下拉框多选添加全选功能

先看效果图 全选&#xff1a; 没有选中时&#xff1a; 选中部分&#xff1a; 作者项目使用的是vue3写法&#xff0c;如果是vue2的自己转换一下 html代码&#xff1a; js代码&#xff1a; 拓展 另一种方法&#xff0c;如果不想使用勾选框&#xff0c;可以试试下面的方…

Bigemap 在林业调研工作中的应用

1. 工作内容&#xff1a;做人工造林计划&#xff0c;生态林业规划设计。 2. 使用场景&#xff1a;主要用 bigemap 软件下载地图 &#xff0c;套合自己用 app 实地采集的数据&#xff0c;比如&#xff1a;林地占 用案件调查&#xff1b;扶贫工作&#xff1b;森林防火灾害面积…

【iOS RunLoop】

文章目录 前言-什么是RunLoop&#xff1f;默认情况下主线程的RunLoop原理 1. RunLoop对象RunLoop对象的获取 CFRunLoopRef源码部分&#xff08;引入线程相关&#xff09; 2. RunLoop和线程3. RunLoop相关的类RunLoop相关类的实现CFRunLoopModeRef五种运行模式CommonModes CFRun…

【力扣】206. 反转链表 <链表指针>

【力扣】206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例 3 输入&#xff1a…

超详细|ChatGPT辅助论文编写教程

本教程讲述在论文编写中使用ChatGPT进行辅助&#xff0c;提供思路&#xff0c;提升效率 祝看到本教程的小伙伴们都完成论文&#xff0c;顺利毕业。 可以加QQ群交流&#xff0c;一群&#xff1a; 123589938 第一章 论文框架搭建 1.1 明确论文题目 1.1.1 适合的研究方向 首先赋…

产品使用手册编写指南工具,我推荐这几个!

在编写产品使用手册时&#xff0c;有许多工具可以帮助我们更高效地完成任务。因为产品手册不仅是一份使用说明书&#xff0c;更是互联网企业宣传和推销自己产品的一种普遍形式&#xff0c;是对产品外观及内容的客观介绍&#xff0c;是对业务操作流程的详细讲解&#xff0c;是对…

【Apollo学习笔记】—— 相机仿真

文章目录 前言相关代码整理 测试实践文件目录包管理BUILD文件以及cyberfile.xml文件源程序BUILD运行结果其他参考CameraOutput channels启动camera驱动启动camera video compression驱动 前言 本文是对Cyber RT的学习记录,文章可能存在不严谨、不完善、有缺漏的部分&#xff0…

uniapp 使用iconfont

问题描述&#xff1a;在开发过程中会遇到使用自定义icon的情况&#xff0c;在uniapp项目中使用iconfont步骤如下。 问题解决 步骤一&#xff1a; 打开iconfont网址------我的项目-----下载至本地 下载成功的文件内容&#xff1a; 步骤二&#xff1a; 以管理员身份运行终端&am…

c++游戏制作指南(二):制作一个炫酷的启动界面(c++绘图)

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f35f;欢迎来到静渊隐者的csdn博文&#xff0c;本文是c游戏制作指南的一部&#x1f35f; &#x1f355;更多文章请点击下方链接&#x1f355; &#x1f368; c游戏制作指南&#x1f3…

SAP 集成以及PO异步接口调优

前言&#xff1a;目前国内的SAP相关的技术文档实在是少得可怜&#xff0c;PO相关的就更少了&#xff0c;基本上都是需要摸索&#xff0c;官方的技术专家很多时候的回复都是说了又似乎没说。。。 背景&#xff1a;由于目标系统接收数据缓慢或者是异步线程出现异常导致错误积压。…

IO流(3)- 转换流与打印流

目录 1. 为什么要用到转换流&#xff1f; 2. 字符输入转换流&#xff08;重点掌握&#xff09; 3. 字符转换输出流&#xff08;理解即可&#xff09; 4. 认识打印流 5. 打印流的作用 6. PrintStream(字节打印流)如何使用&#xff1f; 7. PrintStream 内部没有缓冲区 8. …

Go -- 测试 and 项目实战

没有后端基础&#xff0c;学起来真是费劲&#xff0c;所以打算速刷一下&#xff0c;代码跟着敲一遍&#xff0c;有个印象&#xff0c;大项目肯定也做不了了&#xff0c;先把该学的学了&#xff0c;有空就跟点单体项目&#xff0c;还有该看的书.... 目录 &#x1f34c;单元测试…

【LeetCode】【数据结构】单链表OJ常见题型(二)

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言&#xff1a; 【LeetCode】面试题02.04. 分割链表 【Lee…

Unity实现物体上下漂浮旋转效果

效果展示 代码&#xff08;直接挂载在物体上就可以了&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class FloatingObject : MonoBehaviour {public float amplitude 0.5f; // 上下移动的幅度public float frequency …

C# OpenCvSharp 去水印 图像修复

效果 项目 VS2010.net4.0OpenCvSharp3 代码 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.IO; using System…

简单了解一下vue-router是什么

要学习vue-router就要先知道这里的路由是什么&#xff1f;为什么我们不能像原来一样直接用<a></a>标签编写链接哪&#xff1f;vue-router如何使用&#xff1f;常见路由操作有哪些&#xff1f;等等这些问题&#xff0c;就是本篇要探讨的主要问题。 vue-router是什么…

【二叉树进阶】二叉树的前中后序遍历(非递归迭代实现)

文章目录 1. 二叉树的前序遍历1.1 思路分析1.2 AC代码 2. 二叉树的中序遍历2.1 思路分析2.2 AC代码 3. 二叉树的后序遍历3.1 思路13.2 思路1AC3.3 思路23.4 思路2AC 1. 二叉树的前序遍历 题目链接: link 不用递归&#xff0c;用迭代算法如何实现对二叉树的前序遍历&#xff1f…