Linux多进程和多线程(七)进程间通信-信号量

进程间通信之信号量

资源竞争

多个进程竞争同一资源时,会发生资源竞争。
资源竞争会导致进程的执行出现不可预测的结果。

临界资源

不允许同时有多个进程访问的资源, 包括硬件资源 (CPU、内存、存储器以及其他外
围设备) 与软件资源(共享代码段、共享数据结构)

临界区

多个进程共享的资源被称为临界资源,
这些资源被保护在一个临界区中,
只有进入临界区的进程才能访问临界资源。

信号量

信号量是一种进程间通信机制,用于协调对共享资源的访问。

多进程对stdout资源的竞争

//多进程对stdout资源的竞争

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){

    pid_t cpid;
    cpid = fork();//创建子进程
    if(cpid < 0){
        printf("fork error\n");//fork失败
        exit(EXIT_FAILURE);//EXIT_FAILURE表示程序运行失败
    } else if(cpid == 0){//子进程
        while(1){
            printf("------------------------\n");
            printf("C Start.\n");
            sleep(1);
            printf("C End.\n");
            printf("------------------------\n");
        }
    } else{//父进程
        while(1){
            printf("------------------------\n");
            printf("P Start.\n");
            sleep(1);
            printf("P End.\n");
            printf("------------------------\n");
        }

         wait(NULL); //等待子进程结束
    }



    return 0;
}

代码的输出混乱:

------------------------
P Start.
------------------------
C Start.
P End.
------------------------
C End.
------------------------
------------------------
P Start.
------------------------
C Start.
P End.
C End.
------------------------
------------------------

同步和互斥

互斥

互斥是指进程独占资源,使得其他进程无法访问该资源。

同步

同步是指进程间通信,用于协调进程的执行。
同步在互斥的基础上增加了进程对临界资源的访问顺序
进程主要的同步与互斥手段是信号量

信号量

信号量,由内核维护的整数,其值被限制为大于或等于0;
信号可以执行一下操作:

  • 将信号量设置成一个具体的值;
  • 在信号量当前的基础上加上一个数值;
  • 在信号量当前值的基础上减上一个数值;
  • 等待信号量的值为0;

一般信号量分为

  • 二值信号量:一般指的是信号量值为1,可以理解为只对应一个资源
  • 计数信号量:一般指的是值大于等于2,可以理解为对应多个资源

在linux系统中使用ipcs -s 查询系统中信号量

创建信号量集合

调用 semget() 函数

函数头文件:

#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>


int semget(key_t key, int nsems, int semflg);

函数功能:创建一个信号量集合;

函数参数:

  • key: 信号量集合的键值, 用于标识信号量集合;由ftok()函数生成;
  • nsems: 信号量集合中信号量的个数;
  • semflg: 信号量集合的标志位, 用于设置信号量集合的属性;
    • IPC_CREAT: 如果key对应的信号量集合不存在, 则创建新的信号量集合;
    • IPC_EXCL: 如果key对应的信号量集合已经存在, 则返回-1;
    • 权限标志

函数返回值:

  • 成功: 返回信号量集合的ID;
  • 失败: 返回-1, 并设置errno;
//多进程对stdout资源的竞争

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define MSG_PATH "/home/gopher"
#define MSG_ID 88
int main(){
    key_t key;
    //通过文件路径和ID生成key,
    key= ftok(MSG_PATH,MSG_ID);
    if(key==-1){
        printf("ftok()");
        exit(EXIT_FAILURE);
    }

    //创建信号量集合,包含了一个信号量,编号为0
    int semid=semget(key,1,IPC_CREAT|0666);
    if(semid==-1){
        printf("semget()");
        exit(EXIT_FAILURE);
    }
    
    return 0;
}

创建出一个信号量集合,包含了一个信号量,编号为0

在这里插入图片描述

初始化信号量

调用 semctl() 函数

函数头文件:

#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>


int semctl(int semid, int semnum, int cmd, ... /* arg */ );

函数功能:对信号量集合中的信号量进行操作;根据cmd 决定当前函数的功能;

函数参数:

  • semid: 信号量集合的ID;
  • semnum: 信号量的编号;编号从0开始;
  • cmd: 信号量操作命令;
    • SETVAL:设置信号量的值。
    • GETPID:返回最后一个执行 semop 操作的进程的PID。
    • GETVAL:返回指定信号量的值。
    • GETALL:返回信号量集中所有信号量的值。
    • GETNCNT:返回正在等待信号量增加的进程数。
    • GETZCNT:返回正在等待信号量变为零的进程数。
    • SETALL:设置信号量集中所有信号量的值。
    • IPC_STAT:获取信号量集的状态信息。
    • IPC_SET:设置信号量集的状态信息。
    • IPC_RMID:删除信号量集。
  • … :是属于可变参参数列表,根据不同的命令有不同的参数;

函数返回值:

  • 成功: 根据不同的cmd, 返回不同的结果;

  • GETPID:返回等待最后一个 semop 操作的进程的 PID。

    GETVAL:返回指定信号量的值。
    ls
    GETALL:如果成功,返回 0。

    GETNCNT:返回正在等待增加信号量值的进程数量。

    GETZCNT:返回正在等待信号量值为零的进程数量。

    IPC_STAT:如果成功,返回 0。

    IPC_SET:如果成功,返回 0。

    IPC_RMID:如果成功,返回 0。

    SETVAL:如果成功,返回 0。

    SETALL:如果成功,返回 0。

  • 失败: 返回-1, 并设置errno;

//多进程对stdout资源的竞争

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define MSG_PATH "/home/gopher"
#define MSG_ID 88

union semun{
    int val;
};
int main(){
    key_t key;
    //通过文件路径和ID生成key,
    key= ftok(MSG_PATH,MSG_ID);
    if(key==-1){
        printf("ftok()");
        exit(EXIT_FAILURE);
    }

    //创建信号量集合,包含了一个信号量,编号为0
    int semid=semget(key,1,IPC_CREAT|0666);
    if(semid==-1){
        printf("semget()");
        exit(EXIT_FAILURE);
    }

      union semun s;//定义一个联合体,用于设置信号量的值
      s.val=1;//设置信号量的值为1
      int ret=semctl(semid,0,SETVAL,s);//设置semid信号集中的第编号为0的信号量的值为1
      if(ret==-1){
          printf("semctl()");
          exit(EXIT_FAILURE);
      }

    return 0;
}

信号量操作

  • 信号量可以进⾏以下操作:
    • 对信号量的值加 1
    • 对信号量的值减 1
    • 等待信号量的值为 0

调用 semop() 函数

函数头文件:

#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>


int semop(int semid, struct sembuf *sops, size_t nsops);

函数功能:对信号量集合中的信号量进行操作;

函数参数:

  • semid: 信号量集合的ID;
  • sops: 信号量操作结构体指针
  • nsops: 信号量操作结构体的个数;

函数返回值:

  • 成功: 返回 0;
  • 失败: 返回-1, 并设置errno;

struct sembuf *sops: 信号量操作结构体指针

struct sembuf
{
  unsigned short int sem_num;//信号量编号,从0开始
  short int sem_op;	        //信号量操作
                               //-1:占用资源
                              // +1:释放资源
                              // 0:等待资源
  
  short int sem_flg;		//信号量操作标志位
                              //IPC_NOWAIT:非阻塞,在信号量的值为0时,立即返回
                              // SEM_UNDO:在进程终止时,会自动释放信号量
};

信号量集合删除

调用 semctl() 函数 ,设置命令为 IPC_RMID

在使用 semctl() 函数删除信号量集合时,需要注意第三个参数会被忽略

信号量互斥应用

使用信号量实现进程间互斥,同一时间只有一个进程访问临界资源

1.创建sem.h

#ifndef _mySEM_H_
#define _mySEM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//创建信号量集
int sem_create(int names,unsigned short value[]);
//占用信号量
int sem_p(int semid,int semnum);
//释放信号量
int sem_v(int semid,int semnum);
//删除信号量集
int sem_delete(int semid);


#endif /* _SEM_H_ */

2.创建sem.c

#include "sem.h"

union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };


//创建信号量集
//@param names 信号量集的个数
//@param value 信号量集的初始值
//@return 成功返回信号量集的id,失败返回-1
int sem_create(int names,unsigned short value[]){
    key_t key;
    //创建key
    key= ftok(".",88);
    if (key == -1){
        perror("ftok");
        return -1;
    }

    //创建信号量集
    int semid;
    semid = semget(key,names,IPC_CREAT|0666);//参数:key,信号量集的个数,权限
    if (semid == -1)
    {
        perror("semget");
        return -1;
    }


    union semun s; //定义union semun
    s.array = value;//将value数组赋值给union semun的array成员
    //初始化信号量集
    int ret=semctl(semid,0,SETALL,s);//这个操作将value数组中的值设置到信号量集中
    if (ret == -1){
        perror("semctl");
        return -1;
    }
    
    return semid;


}
    


//占用信号量
//@param semid 信号量集的id
//@param semnum 信号量的编号
int sem_p(int semid,int semnum){
    struct sembuf sem_b;//定义一个信号量操作结构体
    sem_b.sem_num=semnum;//信号量编号
    sem_b.sem_op= -1;//占用资源
    sem_b.sem_flg=SEM_UNDO;//在进程终止时,会自动释放信号量
    //操作1个信号量,如果操作多个信号量,需要创建sembuf结构体的数组
    int r= semop(semid,&sem_b,1); //失败返回-1,并设置errno   
    return r;
}
//释放信号量
int sem_v(int semid,int semnum){
 struct sembuf sem_b;//定义一个信号量操作结构体
    sem_b.sem_num=semnum;//信号量编号
    sem_b.sem_op= 1;//释放资源
    sem_b.sem_flg=SEM_UNDO;//在进程终止时,会自动释放信号量

    int r= semop(semid,&sem_b,1); //操作1个信号量,如果操作多个信号量,需要创建sembuf结构体的数组
    //失败返回-1,并设置errno   
    return r;
}
//删除信号量集
int sem_delete(int semid){
    int r= semctl(semid,0,IPC_RMID); //删除信号量集
    return r;
}

3.创建main.c

// 多进程对stdout资源的竞争

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include "sem.h"
int main()
{

    int semid;// 信号量ID
    unsigned short values[] = {1};// 信号量初始值

    semid = sem_create(1, values);
    if(semid == -1 ){
        printf("sem_create error\n");
        exit(EXIT_FAILURE);
    }




    pid_t cpid;// 子进程ID

    cpid = fork(); // 创建子进程
    if (cpid < 0)
    {
        printf("fork error\n"); // fork失败
        exit(EXIT_FAILURE);     // EXIT_FAILURE表示程序运行失败
    }
    else if (cpid == 0)
    { // 子进程
        while (1)
        {
            sem_p(semid,0);
            printf("------------------------\n");
            printf("C Start.\n");
            sleep(1);
            printf("C End.\n");
            printf("------------------------\n");
            sem_v(semid,0);

        }
    }
    else
    { // 父进程
        while (1)
        {
            sem_p(semid,0);
            printf("------------------------\n");
            printf("P Start.\n");
            sleep(1);
            printf("P End.\n");
            printf("------------------------\n");
            sem_v(semid,0);
        }

        wait(NULL); // 等待子进程结束
    }

    return 0;
}

4.编译运行


------------------------
P Start.
P End.
------------------------
------------------------
C Start.
C End.
------------------------
------------------------
P Start.
P End.
------------------------
------------------------
C Start.
C End.
----------

信号量同步应用

同步在互斥的基础上增加了进程对临界资源的访问顺序
进程主要的同步与互斥手段是信号量

示例:

创建⽗⼦进程,输出 “ABA” 字符串,具体需求如下:
⽗进程 输出 A
⼦进程 输出 B
⽗进程 输出 A ,输出换⾏
能够循环输出 “ABA” 字符

基本思路:

通过创建⼀个信号量集合,包含 2 个信号量,⼀个信号量 编号为 0
(SEM_CONTROL_P)控制⽗进程的运⾏与暂停,⼀个信号量 编号为 1
(SEM_CONTROL_C) 控制⼦进程的运⾏与暂停

// 多进程对stdout资源的竞争

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include "sem.h"

#define SEM_C = 1
#define SEM_P = 0
// todo 创建一个信号量集合,集合中两个信号量,信号量0的值是1,信号量1的值是0;
int main()
{

    int semid;                         // 信号量ID
    unsigned short values[2] = {1, 0}; // 信号量初始值
    // todo 创建一个信号量集合,集合中两个信号量,信号量编号0的值是1,信号量编号1的值是0;
    semid = sem_create(2, values);
    if (semid == -1)
    {
        printf("sem_create error\n");
        exit(EXIT_FAILURE);
    }

    pid_t cpid; // 子进程ID

    cpid = fork(); // 创建子进程
    if (cpid < 0)
    {
        printf("fork error\n"); // fork失败
        exit(EXIT_FAILURE);     // EXIT_FAILURE表示程序运行失败
    }
    else if (cpid == 0)
    { // 子进程
        while (1)
        {
            sem_p(semid, 1); //?占用信号量编号1,信号量编号1的值初始是0 ,在这里阻塞,等待父进程操作
            printf("B");
            fflush(stdout); // 刷新缓冲
            sem_v(semid, 0); //!释放信号量编号0,信号量编号0的值 0=>1,此时父进程不再阻塞,第二次占用0

        }
    }
    else
    { // 父进程
        while (1)
        {
            //@param semid 信号量集的id
            //@param semnum 信号量的编号
            sem_p(semid, 0); //?占用信号量编号0,信号量编号0的值 1=>0
            printf("A");
            fflush(stdout);  // 刷新缓冲
            sem_v(semid, 1); //?释放信号量编号1,信号量编号1的值 0=>1,此时子进程不再阻塞
            sem_p(semid, 0); //!第二次占用信号量编号0,信号量编号0的值是0,在这里阻塞,等待子进程的操作
            printf("A\n");
            fflush(stdout);  // 刷新缓冲
            sem_v(semid, 0);
            sleep(1);

        }

        wait(NULL); // 等待子进程结束
    }

    return 0;
}
0的值 0=>1,此时父进程不再阻塞,第二次占用0

        }
    }
    else
    { // 父进程
        while (1)
        {
            //@param semid 信号量集的id
            //@param semnum 信号量的编号
            sem_p(semid, 0); //?占用信号量编号0,信号量编号0的值 1=>0
            printf("A");
            fflush(stdout);  // 刷新缓冲
            sem_v(semid, 1); //?释放信号量编号1,信号量编号1的值 0=>1,此时子进程不再阻塞
            sem_p(semid, 0); //!第二次占用信号量编号0,信号量编号0的值是0,在这里阻塞,等待子进程的操作
            printf("A\n");
            fflush(stdout);  // 刷新缓冲
            sem_v(semid, 0);
            sleep(1);

        }

        wait(NULL); // 等待子进程结束
    }

    return 0;
}

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

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

相关文章

2024上半年网络工程师考试《应用技术》试题二

试题二(20分) 阅读以下说明,回答问题,将解答填入对应的解答栏内。 某单位网络拓扑如下图所示.SW1、SW2为核心层交换机&#xff0c;PC网关配置在核心层&#xff0c;SW3-SW4为接入层交换机,行政部PC划为vlan10,销售部PC划为vlan20。 【问题1】(4分) 要求实现骨干链路冗余&…

[leetcode hot 150]第一百三十题,被围绕的区域

题目&#xff1a; 给你一个 m x n 的矩阵 board &#xff0c;由若干字符 X 和 O 组成&#xff0c;捕获 所有 被围绕的区域&#xff1a; 连接&#xff1a;一个单元格与水平或垂直方向上相邻的单元格连接。区域&#xff1a;连接所有 0 的单元格来形成一个区域。围绕&#xff1a…

图的应用之最短路径

引入 应用 算法思想 Dijistra算法 用于解决单个顶点间的最短路径问题 将顶点看成两部分&#xff1a; 最短路径顶点集合A与尚未确定最短路径顶点集合B。 先将顶点按最短路径由小到大依次加入到A中&#xff0c;选择由源点到A中最短的顶点&#xff0c;并记录距离与顶点&#xf…

154. 寻找旋转排序数组中的最小值 II(困难)

154. 寻找旋转排序数组中的最小值 II 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;154. 寻找旋转排序数组中的最小值 II 2.详细题解 该题是153. 寻找旋转排序数组中的最小值的进阶题&#xff0c;在153. 寻找旋转排序数组中的最小值…

讲个SystemVerilog随机约束小坑

正文 记录个在写SystemVerilog随机约束时遇到的一个小坑&#xff0c;如果没有认真去查看随机结果是否符合预期&#xff0c;还真不容易发现。 为了方便讲述&#xff0c;写了如下示例代码。类cl_a里有个随机变量aa&#xff0c;初始值为222。在module top里对类cl_a例化并进行约…

【Web】

1、配仓库 [rootlocalhost yum.repos.d]# vi rpm.repo ##本地仓库标准写法 [baseos] namemiaoshubaseos baseurl/mnt/BaseOS gpgcheck0 [appstream] namemiaoshuappstream baseurlfile:///mnt/AppStream gpgcheck0 2、挂载 [rootlocalhost ~]mount /dev/sr0 /mnt mount: /m…

EFUSE中redundancy program/read的理解

现在有空&#xff0c;整理下前段时间关于efuse中redundancy program/read模式的理解&#xff0c;下面以TEF22ULP128X32HD18_PURM这款芯片为例&#xff0c;进行笔记整理&#xff0c;如有侵权或不妥之处&#xff0c;请时告知并及时处理。 1 redundancy的作用 efuse中存放的是芯…

跨平台书签管理器 - Raindrop

传统的浏览器书签功能虽然方便&#xff0c;但在管理和分类上存在诸多局限。今天&#xff0c;我要向大家推荐一款功能强大的跨平台书签管理-Raindrop https://raindrop.io/ &#x1f4e2; 主要功能&#xff1a; 智能分类和标签管理 强大的搜索功能 跨平台支持 分享与协作 卡片式…

适用于 Windows的 5 个最佳 PDF 转 Word 转换器

PDF 文件是共享文档的首选格式&#xff0c;但是&#xff0c;此类文件存在限制&#xff0c;使其难以修改或编辑。因此&#xff0c;您可能会发现自己正在寻找一种将 PDF 文件转换为 Word 或其他可编辑格式的方法。 有许多不同的 PDF 转换器&#xff0c;每个转换器的功能略有不同…

数据结构初阶 遍历二叉树问题(一)

一. 链式二叉树的实现 1. 结构体代码 typedef int BTDateType; typedef struct BinaryTreeNode {BTDateType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right; }BTNode; 大概的图形是这样子 2. 增删查改 我们这里要明确的一点的 二叉树的增删查改是没有意…

04.ffmpeg打印音视频媒体信息

目录 1、相关头文件 2、相关结构体 3、相关函数 4、函数详解 5、源码附上 1、相关头文件 #include <libavformat/avformat.h> 包含格式相关的函数和数据结构 #include <libavutil/avutil.h> 包含一些通用实用函数 2、相关结构体 AV…

【HICE】转发服务器实验

1.在本地主机上操作 2.在客户端操作设置主机的IP地址为dns 3.测试,客户机是否能ping通

基于SpringBoot的招聘信息管理系统的详细设计和实现(源码+lw+部署文档+讲解等,欢迎咨询我!!)

文章目录 目录 文章目录 详细视频展示&#xff1a; 系统具体实现效果&#xff08;看看我的实力&#xff09; 技术栈&#xff08;详细的描述提供给同学思路参考&#xff09; 2.1 Java语言介绍 2.2 B/S架构 2.3 MySQL 数据库介绍 2.4 MySQL环境配置 2.5 SpringBoot框…

ASP.NET Core----基础学习02----中间件的执行顺序 静态文件中间件

文章目录 1.终端中间件&#xff08;Middleware&#xff09;2.中间件的执行顺序&#xff08;1&#xff09;当只有2个中间件的时候&#xff0c;先执行普通中间件&#xff0c;再执行终端中间件&#xff08;2&#xff09;当有多个中间件的时候&#xff0c;中间件的执行顺序 3.添加静…

【ARMv8/v9 GIC 系列 1.5 -- Enabling the distribution of interrupts】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 Enabling the distribution of interruptsGIC Distributor 中断组分发控制CPU Interface 中断组分发控制Physical LPIs 的启用Summary Enabling the distribution of interrupts 在ARM GICv3和GICv4体系结构中&#xff0c;中断分发…

react_web自定义组件_多类型Modal_搜索栏Search

目录 一、带输入框的Modal 二、提示框Modal 三、搜索栏Search 在做项目时引入一些现成的UI组件&#xff0c;但是如果和设计图冲突太大&#xff0c;更改时很麻烦&#xff0c;如果自己写一个通用组件其实也就几十分钟或者几个小时&#xff0c;而且更具UI设计更改也比较好更改&…

最近你悟出来什么道理?

点击上方△腾阳 关注 转载请联系授权 大家伙&#xff0c;我是腾阳。 活了近30年的我&#xff0c;终于领悟到&#xff0c;人生的旅途是一场深刻而复杂的自我发现与灵魂成长的壮丽征途。 这不仅仅是对外在世界的探索&#xff0c;更是内心深处的一场革命&#xff0c;是灵魂从懵…

BulingBuling - 作息安排 [Reset Your Routine] - 1

The Blinkist Team: [ Reset Your Routine ] 如果你发现自己很难按部就班&#xff0c;或者陷入工作效率低的困境&#xff0c;那么你可能需要重新调整一下作息时间&#xff01;从睡眠和营养&#xff0c;到待办事项和井井有条--本指南为你提供了各种技巧和策略&#xff0c;让你的…

餐饮管理系统-计算机毕业设计源码43667

餐饮管理系统 摘 要 在信息化、数字化的时代背景下&#xff0c;餐饮行业面临着前所未有的挑战与机遇。为了提高运营效率、优化顾客体验&#xff0c;餐饮企业亟需一套高效、稳定且灵活的管理系统来支撑其日常运营。基于Spring Boot的餐饮管理系统应运而生&#xff0c;成为餐饮行…

STM32基本定时器、通用定时器、高级定时器区别

一.STM32基本定时器、通用定时器、高级定时器区别 STM32系列微控制器中的定时器资源分为基本定时器&#xff08;Basic Timer&#xff09;、通用定时器&#xff08;General Purpose Timer&#xff09;和高级定时器&#xff08;Advanced Timer&#xff09;三类&#xff0c;它们在…