Linux系统编程系列之POSIX信号量

news2024/12/21 23:11:54

一、什么是POSIX信号量

        POSIX信号量是一种用于线程之间同步和互斥的机制,它是由POSIX标准定义的一种IPC(进程间通信)机制,可以用于进程间或线程间的同步操作。POSIX信号量分成两种,POSIX匿名信号量和POSIX具体信号量。跟管道有点像,有匿名管道和具名管道。

二、特性

        1、POSIX匿名信号量

                (1)、通常用于线程

                (2)、只存在于内存,在文件系统中不可见

        2、POSIX具名信号量

                (1)、通常用在进程

                (2)、存在于文件系统 /dev/shm 中,可被不同进程操作

   三、POSIX信号量使用步骤

        1、POSIX匿名信号量

                (1)、使用sem_init(),初始化匿名信号量

                (2)、使用sem_wait()和sem_post()等,进行 P/V 操作

                (3)、使用sem_destroy(),销毁匿名信号量

        2、POSIX具名信号量

                (1)、使用sem_open(),创建或打开具名信号量、

                (2)、使用sem_wait()和sem_post()等,进行 P/V 操作

                (3)、使用sem_close(),关闭具名信号量

                (4)、使用sem_unlink(),彻底删除不再使用的信号量(可选)

四、相关的函数API接口

        1、定义

// POSIX信号量是一种特性的变量
// 声明一个POSIX信号量s
sem_t s;

        2、初始化和销毁匿名信号量

// 初始化匿名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数sem:待初始化信号量指针
        参数pshared:执行信号的作用范围
            (1)0,作用于进程内的线程间
            (2)非0,作用于进程间

        参数value:信号量的初始值

// 销毁匿名信号量
int sem_destroy(sem_t *sem);

         3、创建和打开具名信号量

// 打开
sem_t *sem_open(const char *name, int oflag);

// 接口说明
        返回值:成功返回信号量的地址,失败返回一个宏SEM_FAILED
        参数name:具名信号量的路径
        参数oflag:与文件打开的参数一样

// 创建
sem_t *sem_open(const char *name, 
                int oflag,
                mode_t mode, unsigned int value);


// 接口说明
        返回值:成功返回信号量的地址,失败返回一个宏SEM_FAILED
        参数name:具名信号量的路径
        参数oflag:与文件打开的参数一样
        参数value:具名信号量初始值

         4、P/V操作

// P操作,阻塞等待申请资源
int sem_wait(sem_t *sem);

// P操作,非阻塞等待申请资源
int sem_trywait(sem_t *sem);

// P操作,在一定时间内进行阻塞等待
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

// V操作,释放资源
int sem_post(sem_t *sem);

         5、关闭具名信号量

        POSIX具名信号量跟文件操作非常类似,打开之后会在内核需要对其维护,因此在不再需要的时候应该关闭

// 关闭具名信号量
 int sem_close(sem_t *sem);

        6、删除具名信号量

        即使所有进程都关闭了信号量并且退出,具名信号量对应的文件是不会消失的,并且会保留所有 P/V 操作的值,如果不再需要这个文件本身,除了可以直接在文件系统中删除外,还可以使用以下接口删除

// 删除具名信号量
int sem_unlink(const char *name);

五、案例

        1、使用POSIX匿名信号量实现线程间数据发送和接收,一条线程发送,一条线程接收

// POSIX匿名信号量的案例

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>


char data[100];

sem_t data_sem; // 声明一个POSIX匿名信号量data_sem

pthread_once_t data_sem_once_init;  // 函数单例变量,用来指定只初始化一次
pthread_once_t data_sem_once_destroy;   // 函数单例变量,用来指定只销毁一次

// 初始化匿名信号量data_sem 
void data_sem_init(void)
{
    // 定义data_sem, 指定用于线程间,初始值0,用来实现同步
    int ret = sem_init(&data_sem, 0, 0);
    if(ret == -1)
    {
        perror("sem_init fail");
    }
}

// 销毁匿名信号量data_sem 
void data_sem_destroy(void)
{
    int ret = sem_destroy(&data_sem);
    if(ret == -1)
    {
        perror("sem_destroy fail");
    }
}


// 线程1的例程函数,用来接收数据
void *recv_routine(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序之后执行一次
    pthread_once(&data_sem_once_init, data_sem_init);

    while(1)
    {
        // P操作,相当于给线程1发送信号
        printf("wait data...\n");
        sem_wait(&data_sem);

        printf("pthread1 read data: %s\n", data);
        memset(data, 0, sizeof(data));
    }

    // 函数单例,本程序之后执行一次
    pthread_once(&data_sem_once_destroy, data_sem_destroy);
}

// 线程2的例程函数,用来发送数据
void *send_routine(void *arg)
{
    printf("I am send_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序之后执行一次
    pthread_once(&data_sem_once_init, data_sem_init);

    while(1)
    {
        printf("please input data:\n");
        fgets(data, 100, stdin);
        

        // V操作,相当于给线程1发送信号
        sem_post(&data_sem);
        printf("pthread2 send data\n");
    }

    // 函数单例,本程序之后执行一次
    pthread_once(&data_sem_once_destroy, data_sem_destroy);
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;

    // 创建线程1,用来接收数据
    errno = pthread_create(&tid1, NULL, recv_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create recv_routine success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create recv_routine fail\n");
    }

  
    errno = pthread_create(&tid2, NULL, send_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create send_routine success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create send_routine fail\n");
    }

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

        2、使用具名POSIX结合共享内存的方式,实现进程间互相收发数据

// POSIX具名信号量的案例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <semaphore.h>
#include <fcntl.h>           
#include <sys/stat.h>       


#define A 1
//#define B 1  // 编译第二版本时,请去掉前面的注释,同时注释A的宏定义

// 注意A进程的P信号量与B进程的V信号量相对应,所以要修改信号量序号的下标
#if A
#define DATA_P_NAME  "sem1"
#define DATA_V_NAME  "sem2"
#define SPACE_P_NAME "sem3"
#define SPACE_V_NAME "sem4"

#elif B
#define DATA_P_NAME  "sem2"
#define DATA_V_NAME  "sem1"
#define SPACE_P_NAME "sem4"
#define SPACE_V_NAME "sem3"
#endif


#define SHM_KEY 0x01
#define SHM_SIZE 4096
// #define SEM_NAME "data_sem2"


int sem_id = -1;
// 映射的虚拟地址
char *shm_addr = NULL;

// 共享内存初始化
int shm_init(void)
{
    // 1、获取KEY值
    key_t shm_key = ftok("./", SHM_KEY);
    if(shm_key == -1)
    {
        perror("ftok fail");
        return -1;
    }

    // 2、指定共享内存,获取共享内存对象ID
    int shm_id = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0666);
    if(shm_id == -1)
    {
        perror("shmget fail");
        return -1;
    }

    // 3、映射共享内存
    shm_addr = (char*)shmat(shm_id, NULL, 0);
    if(shm_addr == (void*)-1)
    {
        perror("shmat fail");
        return -1;
    }
}

// 具名信号量初始化
sem_t *my_sem_init(const char *name, int value)
{
    // 尝试打开具名信号量,并初始化为1
    sem_t *sem = sem_open(name, O_EXCL| O_CREAT, 0666, value);
    // 如果具名信号量存在就不需要初始化,直接打开就行
    if(sem == SEM_FAILED && errno == EEXIST)
    {
        sem = sem_open(name, O_CREAT);
        if(sem == SEM_FAILED)
        {
            perror("sem_open1 fail");
            return NULL;
        }
    }
    else if(sem == SEM_FAILED)
    {
        perror("sem_open2 fail");
        return NULL;   
    }

    return sem;
}    

int main(int argc, char *argv[])
{
    int ret = 0;

    ret = shm_init();
    if(ret == -1)
    {
        return -1;
    }
 
    sem_t *Data_P = my_sem_init(DATA_P_NAME, 0);
    sem_t *Data_V = my_sem_init(DATA_V_NAME, 0);
    sem_t *Space_P = my_sem_init(SPACE_P_NAME, 1);
    sem_t *Space_V = my_sem_init(SPACE_V_NAME, 1);
    
    
    pid_t pid = fork();
    // 父进程负责发送数据
    if(pid > 0)
    {
        while(1)
        {
            // 申请空间,P操作
            printf("wait Space_P...\n");
            sem_wait(Space_P);
            printf("get Space_P\n");

            printf("please input data: \n");
            fgets(shm_addr, SHM_SIZE, stdin);

            // 释放数据,V操作
            sem_post(Data_V);
            printf("set Data_V, send data success\n");
        }
    }
    // 子进程负责接收数据
    else if(pid == 0)
    {
        while(1)
        {
           // 申请数据,P操作
            printf("wait Data_P...\n");
            sem_wait(Data_P);

            printf("read Data: %s", shm_addr);
            memset(shm_addr, 0, SHM_SIZE);

            // 释放空间,V操作
            sem_post(Space_V);
            printf("set Space_V\n");
        }
    }
    else
    {
        perror("fork fail");
        return -1;
    }

    return 0;
}

        注意:编译时编译两个版本,第一个版本直接编译,另外一个版本需要展开B的宏定义后再编译,跟前几篇的信号量组的案例一样。这个案例有点bug,每次需要重新把/dev/shm/下四个具名信号量都删除了才能重新运行中,目前努力修复中。

六、总结

        POSIX信号量是一种用于线程之间同步和互斥的机制,当然也可以用于进程间通信。POSIX信号量有匿名信号量和具名信号量,跟管道有点像,两种信号量的使用步骤需要遵循一定的步骤,使用方法不一样。

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

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

相关文章

S-Clustr(影子集群)可控制嵌入式设备和个人电脑的僵尸网络工具

公告 项目地址:https://github.com/MartinxMax/S-Clustr/tree/V1.0.0 1.成功扩展3类嵌入式设备,组建庞大的"僵尸网络" |——C51[开发中] |——Arduino |——合宙AIR780e[开发中] 2.攻击者端与服务端之间通讯过程全程加密,防溯源分析 3.Generate一键自动生成Arduino…

deterministic=True requires SQLite 3.8.3 or higher

文章目录 出错来源解决方法 出错来源 在我的centos服务器执行Django网站 python manage.py runserver报错&#xff1a; deterministicTrue requires SQLite 3.8.3 or higher解决方法 pip3 install pysqlite3 pip3 install pysqlite3-binary找到下载的模块路径&#xff0c;修…

十天学完基础数据结构-第二天(数据结构简介)

什么是数据结构&#xff1f; 在计算机科学中&#xff0c;数据结构是一种组织和存储数据的方式。它定义了数据的布局&#xff0c;以及对这些数据执行的操作。你可以把数据结构看作是计算机内存中的特定组织方式&#xff0c;就像图书馆中书籍的排列一样。 数据结构可以是各种形…

STM32复习笔记(一):软件配置工程创建

目录 Preface&#xff1a; Hardware-Configuration & Software-Environment&#xff1a; &#xff08;一&#xff09;新建项目工程 &#xff08;二&#xff09;工程配置 &#xff08;三&#xff09;配置外设 &#xff08;四&#xff09;项目管理 &#xff08;五&…

qml保姆级教程二:qml基本数据类型

&#x1f482; 个人主页:pp不会算法v &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 QML系列教程 QML教程一&#xff1a;布局组件 文章目录 boolrealdouble…

Jmeter如何做压力测试

1.哪些业务需要做压力测试&#xff1f; 比较常用的业务场景或功能模块 单业务场景或多业务场景 项目要求做的业务场景 2.压力测试的并发数是多少&#xff1f; 有预期的数值&#xff1f;100 200 300一次性达到还是逐次增加&#xff1f;参照上次性能测试的结果 3.关注哪些参…

【代码随想录】LC 704. 二分查找

文章目录 前言一、题目1、原题链接2、题目描述 二、解题报告1、思路分析2、时间复杂度3、代码详解 三、知识风暴 前言 本专栏文章为《代码随想录》书籍的刷题题解以及读书笔记&#xff0c;如有侵权&#xff0c;立即删除。 一、题目 1、原题链接 704. 二分查找 2、题目描述 二…

cmake安装

Windows下安装Cmake教程 注意下载二进制版本的&#xff1a;

将3D MAX设计模型导入NX1988

将3D MAX设计模型导入NX1988 概述导入流程导出喜欢的模型对模型进行修改模型贴图 概述 一般家装设计都不会用NX之类的产品设计软件&#xff0c;也没有通用的文件格式可以互相转换&#xff0c;本文的目的是将从网上下载的一些设计较好的3D MAX模型导入到NX软件中借用&#xff0…

【11】c++设计模式——>单例模式

单例模式是什么 在一个项目中&#xff0c;全局范围内&#xff0c;某个类的实例有且仅有一个&#xff08;只能new一次&#xff09;&#xff0c;通过这个唯一的实例向其他模块提供数据的全局访问&#xff0c;这种模式就叫单例模式。单例模式的典型应用就是任务队列。 为什么要使…

Java新特性中的Preview功能如何运行和调试

在每个Java新版本发布的特性中&#xff0c;都会包含一些Preview&#xff08;预览&#xff09;功能&#xff0c;这些功能主要用来给开发者体验并收集建议。所以&#xff0c;Preview阶段的功能并不是默认开启的。 如果想体验某个Java版本中的Preview功能&#xff0c;您还需要做一…

C# 集合

C# 集合 集合集合接口和类型列表队列栈链表有序表字典LoopupHashSet位数组 集合 数组的大小是固定的。如果元素个数是动态的&#xff0c;就应使用集合类。List 和 ArrayList 是与数组相当的集合类。还有其他类型的集合&#xff1a;队列、栈、链表和字典。 集合接口和类型 集…

FL Studio21.1.0水果中文官方网站

FL Studio 21.1.0官方中文版重磅发布纯正简体中文支持&#xff0c;更快捷的音频剪辑及素材管理器&#xff0c;多样主题随心换&#xff01;Mac版新增对苹果M2/1家族芯片原生支持。DAW界萌神&#xff01;极富二次元造型的水果娘FL chan通过FL插件Fruity Dance登场&#xff0c;为其…

数据结构-----平衡二叉树

目录 前言 1.平衡二叉树 1.1概念与特点 1.2与二叉排序树比较 1.3判断平衡二叉树 2.平衡二叉树的构建 2.1平衡因子 BF 2.2 LL型失衡&#xff08;右旋&#xff09; 2.3 RR型失衡&#xff08;左旋&#xff09; 2.4 LR型失衡&#xff08;先左旋再右旋&#xff09; 2.5 RL…

springboot 集成 PageHelper 分页失效

前言 项目启动初期&#xff0c;在集成mybatis的分页插件&#xff0c;自定义封装了一个分页的工具类&#xff0c;方便后期项目的扩展。部分的代码如下&#xff1a; /*** 分页查询* 进行count计算** param pageNum 页数* param pageSize 每页数量* param supplier 查询操作* re…

Redis相关概念

1. 什么是Redis&#xff1f;它主要用来什么的&#xff1f; Redis&#xff0c;英文全称是Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提…

基于Java的毕业设计选题管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

44 二叉搜索树中第K个小的元素

二叉搜索树中第K个小的元素 题解1 中序遍历题解2 AVL&#xff08;手撕平衡二叉树&#xff1a;谢谢力扣官方&#xff09; 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 个最小元素&#xff08;从 1 开始计数&#xf…

免杀对抗-成品EXE免杀-反特征码-通用跳转

一、exe程序生成 1.使用如下shellcode加载器&#xff0c;生成c/c语言的exe程序 加载器&#xff1a;1.c #include <Windows.h> #include <stdio.h>#pragma comment(linker,"/section:.data,RWE")unsigned char shellcode[] 生成的shellcode;int main() { …

【一、灵犀考试系统项目设计、框架搭建】

一、创建数据库 1、打开power designer&#xff0c;新建数据库模型 2、新建数据表&#xff0c;以及关系 【注意】 图片的类型有两种&#xff1a;varbinary 和 image varbinary : 二进制字节流&#xff0c;可以自动控制长度 image : 最大可放2G图片 3、创建数据库&#…