opencv 打开图片后,cv::mat存入共享内存的代码,实现消费者与生产者模型。XSI信号量和POSIX 信号量

news2024/12/26 22:07:40

文章目录

  • 基于 sys 系统信号量(XSI信号量)
    • 常用api
    • 参考
  • 基于 POSIX 信号量
    • 有名信号量
      • 常用 api
    • 无名信号量
      • 常用 api
    • 参考
  • 实践-基于POSIX有名信号量
    • 生产者消费者模型任务说明
      • 同步关系
      • 互斥关系
    • 设置一个互斥信号量,实现对共享内存的互斥访问
    • 设置两个信号量,用于标记资源的数目,实现进程间的两个同步关系
      • 发送
      • 接受
    • 参考的文心一言的伪代码
  • 代码分享
  • 下一步
    • 1、如何设置队列?
    • 2、使用STL 容器在共享内存怎么搞?
    • 3、消息队列,例如POSIX 消息队列

基于 sys 系统信号量(XSI信号量)

Linux内核提供了一些系统调用函数来创建和操作信号量,这些函数都定义在<sys/sem.h>头文件中。

XSI信号量相对于POSIX信号量要复杂很多,因为其以下特性:

(1) 信号量并非单个非负值,而是一个或多个信号量的集合。创建信号量时,要指定其中信号数量;

(2) 创建信号量(semget)和初始化信号量(semctl)相互独立,不能原子操作;

(3) XSI信号量不会在进程退出后自动销毁(所有XSI IPC都有这个毛病,包括消息队列,共享存储,信号量)。

每个信号量集合含有一个semid_ds结构体

struct semid_ds
{
struct ipc_perm sem_perm;
unsigned short sem_nsems; //信号数量
time_t sem_otime;
time_t sem_ctime;
//可能还有其他成员
}

常用api

函数名:semget
nsems表示信号集合的信号数量,如果创建新集合,指定nsems;如果引用现有集合,nsems=0
*/
int semget(key_t key,int nsems,int flag); 

/*
函数名:semctl
设置选项,最后一个union参数可选,根据cmd的命令
cmd命令:
IPC_STAT:取semid_ds结构体,存于arg.buf中
IPC_SET:按照arg.buf中的semid_ds结构体,设置sem_perm.uid,sem_perm.gid,sem_perm.mode
IPC_RMID:删除信号量集合,跟共享存储的IPC_RMID不同,此处立即删除,其他还在引用的进程再次使用该信号量集合时报错EIDRM
GETVAL:获取semnum的semval值
SETVAL:设置成员semnum的semval
GETPID:获取成员semnum的sempid
GETNCNT:获取semnum的semcnt
GETALL:获取所有信号量的semval,保存在arg.array中
SETALL:将所有信号量的semval设置为对应的arg.array值
*/
int semctl(int semid,int semnum,int cmd,.../*union semun arg*/);
union semun
{
    int val; //for SETVAL
    struct semid_ds* buf; //for IPC_STAT and IPC_SET
    unsigned short *array //for SETALL and GETALL
}
/*
函数名:semop(自动执行信号量集合上的操作数组)
semoparray:sembuf的数组
nops:semoparray中元素个数
*/
int semop(int semid,struct sembuf semoparray[],size_t nops); //sem_buf定义如下:
struct sem_buf
{
  usigned short sem_num; //member int set,即信号量集合中信号的序号
  short sem_op; //operations
  short sem_flag;  //IPC_NOWAIT,SEM_UNDO
};

参考

https://blog.csdn.net/BrilliantAntonio/article/details/120606129
https://zhuanlan.zhihu.com/p/642216249
https://zhuanlan.zhihu.com/p/621670727

基于 POSIX 信号量

基于的头文件是#include <semphore.h>

在 POSIX标准中,信号量分两种,一种是无名信号量,一种是有名信号量。 无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥。 有名信号量和无名信号量的差异在于创建和销毁的形式上,但是其他工作一样,无名信号量则直接保存在内存中, 而有名信号量则要求创建一个文件。

抽象的来讲,信号量中存在一个非负整数,所有获取它的进程/线程都会将该整数减一, 当该整数值为零时,所有试图获取它的进程/线程都将处于阻塞状态。通常一个信号量的计数值用于对应有效的资源数, 表示剩下的可被占用的互斥资源数。其值的含义分两种情况:

  • 0:表示没有可用的信号量,进程/线程进入睡眠状态,直至信号量值大于 0。

  • 正值:表示有一个或多个可用的信号量,进程/线程可以使用该资源。进程/线程将信号量值减1, 表示它使用了一个资源单位。

对信号量的操作可以分为两个:

  • P 操作:如果有可用的资源(信号量值大于0),则占用一个资源(给信号量值减去一,进入临界区代码); 如果没有可用的资源(信号量值等于0),则被阻塞,直到系统将资源分配给该进程/线程(进入等待队列, 一直等到资源轮到该进程/线程)。这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样, P操作就是申请资源,如果申请成功,资源数(空闲的停车位)将会减少一个,如果申请失败,要不在门口等,要不就走人。

  • V 操作:如果在该信号量的等待队列中有进程/线程在等待资源,则唤醒一个阻塞的进程/线程。如果没有进程/线程等待它, 则释放一个资源(给信号量值加一),就跟你从停车场出去的时候一样,空闲的停车位就会增加一个。

有名信号量

如果要在Linux中使用信号量同步,需要包含头文件<semaphore.h>。

有名信号量其实是一个文件,它的名字由类似 " sem.[信号量名字] " 这样的字符串组成,注意看文件名前面有" sem. ", 它是一个特殊的信号量文件,在创建成功之后,系统会将其放置在 /dev/shm 路径下,不同的进程间只要约定好一个相同的信号量文件名字,就可以访问到对应的有名信号量,并且借助信号量来进行同步或者互斥操作,需要注意的是,有名信号量是一个文件,在进程退出之后它们并不会自动消失,而需要手动删除并释放资源。

常用 api

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);

无名信号量

无名信号量的操作与有名信号量差不多,但它不使用文件系统标识,直接存在程序运行的内存中, 不同进程之间不能访问,不能用于不同进程之间相互访问。

常用 api

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);

参考

https://blog.csdn.net/qq_53508543/article/details/135471988

实践-基于POSIX有名信号量

上一篇博客写了共享内存,给了大家示例的代码。《opencv 打开图片后,cv::mat存入共享内存的代码,以及如何设置共享内存的大小?图片的3840x2160 pixels》,基于此进行生产者消费者尝试。

生产者消费者模型任务说明

生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个著名的进程同步问题的经典案例。它描述的是有一组生产者进程在生产产品,并将这些产品提供给一组消费者进程去消费。为使生产者进程和消费者进程能够并发执行,在这两者之间设置里一个具有 n 个缓冲区的缓冲池,生产者进程将他所生产的的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品并进行消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但亡们之间必须保特同步,即不允许消费者进程到一个空缓冲区中去取产品;也不允许生产者进程向一个已装满产品且产品尚未被取走的缓冲区投放产品。

思路分析
我们分析题目中的同步和互斥关系:

同步关系

当缓冲区有空位时,生产者进程才可以生产
当缓冲区有产品是,消费者进程才可以消费

互斥关系

生产者进程与消费者进程对缓冲区的访问是互斥的

在这里插入图片描述整体思路

总体思路如下:

  • 设置一个生产者进程,负责生产产品
  • 设置一个消费这进程,负责消费产品
  • 生产者与消费者进程间的通讯通过共享内存实现
  • 设置一个互斥信号量,实现对共享内存的互斥访问
  • 设置两个信号量,用于标记资源的数目,实现进程间的两个同步关系

设置一个互斥信号量,实现对共享内存的互斥访问

参考例子

#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
 
int main(int argc, char **argv)
{
    int pid;
    sem_t *sem;
    const char sem_name[] = "my_sem_test";
    pid = fork();
    if (pid < 0) {
        printf("error in the fork!\n");
    }
    /* 子进程 */
    else if (pid == 0) {
        /*创建/打开一个初始值为1的信号量*/
        sem = sem_open(sem_name, O_CREAT, 0644, 1);
        if (sem == SEM_FAILED) {
            printf("unable to create semaphore...\n");
            sem_unlink(sem_name);
            exit(-1);
        }
        /*获取信号量*/
        sem_wait(sem);
        for (int i = 0; i < 3; ++i) {
            printf("child process run: %d\n", i);
            /*睡眠释放CPU占用*/
            sleep(1);
        }
    /*释放信号量*/
    sem_post(sem);
    }
    /* 父进程 */
    else {
        /*创建/打开一个初始值为1的信号量*/
        sem = sem_open(sem_name, O_CREAT, 0644, 1);
        if (sem == SEM_FAILED) {
            printf("unable to create semaphore...\n");
            sem_unlink(sem_name);
            exit(-1);
        }
        /*申请信号量*/
        sem_wait(sem);
        for (int i = 0; i < 3; ++i) {
            printf("parent process run: %d\n", i);
            /*睡眠释放CPU占用*/
            sleep(1);
        }
        /*释放信号量*/
        sem_post(sem);
        /*等待子进程结束*/
        wait(NULL);
        /*关闭信号量*/
        sem_close(sem);
        /*删除信号量*/
        sem_unlink(sem_name);
    }
    return 0;
}

设置两个信号量,用于标记资源的数目,实现进程间的两个同步关系

一个表示空槽位(empty slots)的信号量(empty),用于同步生产者和消费者。当缓冲区为空时,消费者必须等待生产者生成数据。
一个表示满槽位(full slots)的信号量(full),也用于同步生产者和消费者。当缓冲区已满时,生产者必须等待消费者消费数据。

发送

考虑到只有一个内存,存满了就没有了,因此设置信号量队列是1。

    sem_t  * sem_empty = sem_open("/sem_name1", O_CREAT, 0644, 1);
    if (sem_empty == SEM_FAILED) {
        printf("unable to create semaphore...\n");
        sem_unlink("/sem_name1");
        exit(-1);
    }

    sem_t  * sem_full = sem_open("/sem_name2", O_CREAT, 0644, 0);
    if (sem_full == SEM_FAILED) {
        printf("unable to create semaphore...\n");
        sem_unlink("/sem_name2");
        exit(-1);
    }

处理过程

    // wait for empty slot
    sem_wait(sem_empty);

    // get lock
    sem_wait(sem);
    memcpy(mem, image.data, image.total() * image.elemSize());
    sem_post(sem);

    // tell customer to use
    sem_post(sem_full);

接受

    sem_t  * sem_empty = sem_open("/sem_name1", 0);
    if (sem_empty == SEM_FAILED) {
        printf("unable to create semaphore...\n");
        sem_unlink("/sem_name1");
        exit(-1);
    }

    sem_t  * sem_full = sem_open("/sem_name2",0);
    if (sem_full == SEM_FAILED) {
        printf("unable to create semaphore...\n");
        sem_unlink("/sem_name2");
        exit(-1);
    }

处理过程

    // wait
    sem_wait(sem_full);
    // get lock
    sem_wait(sem);
    cv::Mat image(height, width, type, mem);
    sem_post(sem);
     // tell producer to make
    sem_post(sem_empty);

参考的文心一言的伪代码

// 初始化信号量  
semaphore mutex = 1; // 互斥信号量,初始化为1,表示没有进程在访问共享缓冲区  
semaphore empty = N; // 空槽位信号量,初始化为缓冲区大小N  
semaphore full = 0;  // 满槽位信号量,初始化为0,因为缓冲区开始时是空的  
  
// 生产者进程  
while (true) {  
    // 生产数据项  
    item = produce_item();  
  
    // 等待缓冲区有空槽位  
    P(empty); // P操作,即等待(wait)信号量empty变为非零,然后将其减1  
  
    // 访问共享缓冲区(临界区)  
    P(mutex); // 等待mutex变为非零,然后将其减1,进入临界区  
    // 将数据放入缓冲区  
    put_item_into_buffer(item);  
    V(mutex); // V操作,即释放(signal)mutex,将其加1  
  
    // 通知消费者缓冲区有数据  
    V(full); // 释放full信号量,将其加1  
}  
  
// 消费者进程  
while (true) {  
    // 等待缓冲区有数据  
    P(full); // 等待full变为非零,然后将其减1  
  
    // 访问共享缓冲区(临界区)  
    P(mutex); // 等待mutex变为非零,然后将其减1,进入临界区  
    // 从缓冲区取出数据  
    item = get_item_from_buffer();  
    V(mutex); // 释放mutex信号量,将其加1  
  
    // 处理数据项  
    consume_item(item);  
  
    // 通知生产者缓冲区有空槽位  
    V(empty); // 释放empty信号量,将其加1  
}

代码分享

https://gitee.com/hiyanyx/share-memory-mmap-opencv/tree/Producer-consumer

下一步

1、如何设置队列?

目前是一个内存,存满了就等待,如何处理呢?

写完了博客,链接:https://blog.csdn.net/djfjkj52/article/details/139806993

2、使用STL 容器在共享内存怎么搞?

STL的容器很方便使用啊,要是能联合,岂不乐哉
写完了博客,链接:https://blog.csdn.net/djfjkj52/article/details/139805285

3、消息队列,例如POSIX 消息队列

<sys/msg.h> 是 Unix 和类 Unix 系统(如 Linux)中用于支持 POSIX 消息队列的头文件。

  • 消息队列是在消息的传输过程中保存消息的容器。它通常是在内核空间中维护的,进程通过特定的标识符(如msgid)对消息队列进行读写操作。

  • 消息队列中的信息通常是一个结构体,包含了消息的种类(类似身份证)和消息的内容。通过这种方式,多个进程间可以通过消息的种类来进行通讯,实现了进程间的定向信息传输。
    -消息队列克服了信号承载信息量少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • 在传输数据方面,消息队列可以使用UDP进行传输。UDP为应用程序提供了一种无需建立连接就可以发送封装的IP数据包的方法。
    消息队列可以基于多种机制进行数据传输,主要可以分为两大类:

    内存中的消息队列:
    这类消息队列直接在系统内存中创建和管理消息,不需要通过网络传输。它们通常用于在同一台机器上的不同进程间进行通信。
    例如,某些消息队列系统(如RabbitMQ、ActiveMQ等)可以在本地机器上运行,通过进程间的共享内存或其他内存管理机制进行消息的存储和传输。
    基于网络的消息队列:
    这类消息队列通过网络协议(如TCP/IP、UDP等)进行消息的传输,允许在不同机器上的进程间进行通信。
    TCP/IP:TCP/IP协议提供了可靠的、面向连接的字节流传输服务。基于TCP/IP的消息队列系统可以确保消息的完整性和顺序性,即使在网络条件不佳的情况下也能保证消息的可靠传输。
    UDP:虽然UDP协议本身不提供可靠的数据传输保证,但某些消息队列系统(如Kafka)可以选择使用UDP进行数据传输。UDP传输速度快,适用于对性能要求极高且对消息丢失或乱序不太敏感的场景。

此外,消息队列的传输方式还取决于具体的消息队列系统和其配置。例如:

  • Kafka:Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量。它通常使用TCP/IP协议进行数据传输,但也可以配置为使用其他协议。
  • RabbitMQ:RabbitMQ是基于AMQP(高级消息队列协议)的开源消息队列系统。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景。它同样使用TCP/IP协议进行数据传输。

归纳来说,消息队列可以基于内存或网络进行数据传输。在网络传输方面,TCP/IP协议由于其可靠性和稳定性而被广泛采用,而UDP则因其高性能和轻量级在某些场景下被选择使用。但具体使用哪种传输方式,还需要根据实际需求、系统架构和网络环境来综合考虑。

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

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

相关文章

ESP32 矩阵键盘 4*3状态机

简洁高效的ESP32处理矩阵键盘代码… /**********矩阵键盘IO映射***************3(9) 1(8) 5(4)2(13)7(12)6(18)4(19)*************************************/ uint8_t Trg0,Cont0; void Key_Task(void) {uint8_t ReadData,ColumnData,RowData;pinMode(9,INPUT_PULLUP);pin…

[面试题]RabbitMQ

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…

候选键的确定方法-如何判断属性集U的子集K是否为候选键、如何找到关系模式的候选键

一、候选键的定义 在关系模式R(U,F)中&#xff0c;若&#xff0c;且K满足&#xff0c;则K为关系模式R的候选键 关系模式R的候选键必须满足以下两个条件&#xff1a; &#xff08;1&#xff09;必须是属性集U的子集 &#xff08;2&#xff09;完全函数决定属性集U 二、如何…

使用opencv合并两个图像

本节的目的 linear blending&#xff08;线性混合&#xff09;使用**addWeighted()**来添加两个图像 原理 (其实我也没太懂&#xff0c;留个坑&#xff0c;感觉本科的时候线代没学好。不对&#xff0c;我本科就没学线代。) 源码分析 源码链接 #include "opencv2/imgc…

工控 UI 风格美轮美奂

工控 UI 风格美轮美奂

Docker 部署项目,真的太雅了~

大家好&#xff0c;我是南城余&#xff01; 最近在找工作&#xff0c;正好手里有台服务器&#xff0c;之前项目上线用的宝塔部署项目上线&#xff0c;在公司实习了一年后&#xff0c;发现如今项目部署都使用的是容器化部署方案&#xff0c;也就是类似于和 Docker 一样的部署方案…

PFC 离散元数值模拟仿真技术与应用

近几年&#xff0c;随着计算能力的提高和算法的优化&#xff0c;离散元仿真技术得到了快速发展&#xff0c;并在学术界产生了大量研究成果。在 PFC 离散元计算中无需给定材料的宏观本构关系和对应的参数&#xff0c;这些传统的参数和力学特性在程序中可以自动得到。据调查&…

【绝对有用】刚刚开通的GPT-4o计算这种数学题目出现问题了

欢迎关注如何解决以上问题的方法&#xff1a;查看个人简介中的链接的具体解决方案

Matlab数学建模实战应用:案例2 - 传染病传播

目录 前言 一、问题分析 二、模型建立 三、Matlab代码实现 四、模型验证 灵敏度分析 五、模型应用 实例总结 总结 前言 传染病传播模型是公共卫生和流行病学的重要研究内容&#xff0c;通过数学建模可以帮助我们理解传染病的传播规律和趋势&#xff0c;以便制定有效的…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 机器人搬砖(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

全网最易懂,开源时序数据库influxDB,实际应用评测

前言&#xff1a; 当今是信息爆炸的时代&#xff0c;在处理高频数据时&#xff0c;关系型数据库oracle/mysql明显表现出乏力&#xff0c;因秒级、毫秒级高频数据&#xff0c;分分钟可以把关系型数据库的表塞爆。在日常生活工作中&#xff0c;我们经常会遇到哪些需要高频分析的场…

令人震撼的人类智慧的科学领域-AI技术

AI&#xff0c;全称为人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;是一门致力于让机器模仿人类智慧的科学领域。其核心技术涵盖了机器学习、自然语言处理、计算机视觉及专家系统等多个方面。AI旨在开发能够感知环境、进行逻辑推理、自主学习并做出决策…

Leetcode 力扣124. 二叉树中的最大路径和 (抖音号:708231408)

二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root &#xff0c…

基于JSP的二手交易平台网站

开头语&#xff1a; 你好&#xff0c;我是计算机专业的学长猫哥。如果你对二手交易平台感兴趣或有开发相关需求&#xff0c;欢迎联系我&#xff0c;我的联系方式见文末。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;ECLI…

vue3 antdv Select 实现输入关键词,通过服务器去查询数据,并显示到表格中的实现思路。

实现思路&#xff1a; 1&#xff09;输入关键词&#xff0c;通过Select的查询事件&#xff08;onSearch&#xff09;来到服务器查询数据。 2&#xff09;根据查询到的数据显示到表格中&#xff0c;然后通过表格的&#xff08;cellClickEvent&#xff09;事件来选择相关的用户…

第四届人工智能、机器人和通信国际会议(ICAIRC 2024)

第四届人工智能、机器人和通信国际会议&#xff08;ICAIRC 2024&#xff09; 2024 4th International Conference on Artificial Intelligence, Robotics, and Communication 2024年12月27-29日 | 中国厦门 重要信息 会议官网&#xff1a;www.icairc.net 录用通知时间&…

视觉应用线扫相机速度反馈(倍福CX7000PLC应用)

运动控制实时总线相关内容请参考运动控制专栏,这里不再赘述 1、运动控制常用单位u/s运动控制单位[u/s]介绍_运动控制 unit是什么单位-CSDN博客文章浏览阅读176次。运动控制很多手册上会写这样的单位,这里的u是英文单词unit的缩写,也就是单位的意思,所以这里的单位不是微米…

阿里云服务器提醒漏洞要不要打补丁?

我们自己用的电脑一旦发现漏洞&#xff0c;往往是第一时间进行打补丁重启等等&#xff0c;但是作为服务器而言&#xff0c;往往没有这个习惯&#xff0c;为什么&#xff1f;因为害怕服务器打补丁以后&#xff0c;重启后出现打不开的情况&#xff0c;毕竟稳定的运行似乎在这种情…

免费企业级日志采集工具

免费试用下载: Gitee下载 最新版本 优势: A. 开箱即用. 解压直接运行.不需额外安装. B. 批管理设备. 设备配置均在后台管理. C. 无人值守 客户端自启动,自更新. D. 稳定安全. 架构简单,内存占用小,通过授权访问.

rust数据类型

一&#xff0c;基本类型 1&#xff0c;基本类型 &#xff08;1&#xff09;整数类型 let x111_222_3334;let y:u161123; 整数默认是i32类型&#xff0c;整数中间的下划线可以忽略。 &#xff08;2&#xff09;浮点数 在 Rust 中浮点类型数字也有两种基本类型&#xff1a; …