Linux应用编程---10.信号量

news2024/11/28 14:31:53

Linux应用编程—10.信号量

​ 信号量用于任务间的同步!简单来理解,信号量是一个被内核维护的整数,这个整数一般是“大于等于零”的,我们对这个信号量的操作一般为:将信号量设置一个值、发布(加上一个信号量)、消耗(减去一个信号量)、等待信号量的值为0。在POSIX下信号量分为命名信号量与未命名信号量。未命名信号量,也被称为基于内存的信号量,类型为sem_t,创建未命名信号量使用的函数为sem_init()。

10.1 初始化一个信号量

​ 初始化信号量函数sem_init(),在Linux终端下查看该函数。

NAME
       sem_init - initialize an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       Link with -pthread.

​ 该函数用来初始化一个无名信号量,使用时包含头文件semaphore.h,编译时需要跟上后缀-pthread。函数传参有3个,sem_t *sem是sem_t类型的地址,可以理解为我们创建信号量的id,int pshared,这个参数决定了我们创建的信号量是在进程间共享还是单个进程中的线程间共享。unsigned int value是信号量的初始值,一般为一个大于等于0的整数。

10.2 销毁一个信号量

​ sem_destroy()函数用来销毁信号量,被销毁的信号量是之前使用sem_init()初始化的未命名信号量。

10.3 等待一个信号量

​ sem_wait()函数用来等待一个信号量,此时会对信号量-1,如果信号量的当前值大于0,则该函数立即返回。如果等于0,该函数会阻塞调用进程直到信号量的值大于0为止,信号量值大于0时该信号量值被-1并且函数返回。

10.4 发布一个信号量

​ 发布一个信号量时,使用函数sem_post()函数。该函数会将信号量+1。

10.5 创建一个映射mmap()函数

​ mmap()函数用来在调用进程的虚拟地址空间中创建一个新映射。

NAME
       mmap, munmap - map or unmap files or devices into memory

SYNOPSIS
       #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
       int munmap(void *addr, size_t length);

       See NOTES for information on feature test macro requirements.

​ 调用该函数需要包含头文件sys/mman.h,函数原型为:void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);其中addr参数指定该映射被放置的虚拟地址,将该参数赋值为NULL,那么内核会为映射选择一个合适的地址;length参数指定映射的字节数;port参数可以写入:PROT_NONE:区域无法访问、PROT_READ:区域内容可读取、PROT_WRITE:区域内容可修改、PROT_EXEC:区域内容可执行。这个映射地址会用来存放信号量id,所以要可读可写;flags参数是一个控制映射操作各个法面的选项的位掩码,可写入:MAP_PRIVATE:创建一个私有映射;MAP_SHARED:创建一个共享映射;fd与offset用于文件映射,fd参数是一个标识被映射的文件的文件描述符;offset参数指定了映射在文件中的起点。默认可以填入0。

10.6 信号量编程实践

​ 初始化一个信号量,它在父子进程之间被共享。共享方式可以使用创建共享内存或者使用mmap()函数。创建一对父子进程,父进程里面每隔1秒钟,等待一个信号量,然后打印字符串。子进程中每隔5秒钟打印字符串,并且发布一个信号量。由于该信号量初始化时,初始值为1,则父进程只执行一次后因为信号量=0而被sem_wait()阻塞。当子进程执行后,会发布信号量,此后父进程继续执行。

​ 代码如下。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>

int main(void)
{
        pid_t pid;
        sem_t *sem_id = NULL;

        sem_id = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

        sem_init(sem_id, 1, 1);

        pid = fork();

        if(pid > 0)
        {
                while(1)
                {
                        sem_wait(sem_id);
                        printf("Parent process.\n");
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("Chiled process.\n");
                        sem_post(sem_id);
                        sleep(5);
                }
        }
        else
                perror("fork.");


        return 0;
}

​ 运行结果:

image-20221213002841908

图1 运行结果

10.7 线程间信号量同步

​ sem_init()函数的原型如下,int sem_init(sem_t *sem, int pshared, unsigned int value);其中第二个参数pshared,它决定了这个初始化的信号量是在进程的线程之间还是两个进程之间。当pshared = 0时,初始化创建的信号量在线程之间,因为线程之间资源是共享的,所以这个信号量的地址只需要是一个全局变量即可,不在需要mmap()函数去创建映射。

​ 创建两个线程,线程1等待信号量,然后打印字符串“thread_1”,线程2每隔3秒打印字符串并且发布一个信号量。程序预期结果是,每隔3秒,线程2发布信号量后线程1才执行。

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

void *thread_fun1(void *arg);
void *thread_fun2(void *arg);

sem_t sem_id;

int main(void)
{

        int ret = 0;
        pthread_t thread1;
        pthread_t thread2;

        sem_init(&sem_id, 0, 0);

        ret |= pthread_create(&thread1, NULL, thread_fun1, NULL);

        if(0 != ret)
        {
                perror("thread_create.");
                exit(1);
        }

        ret |= pthread_create(&thread2, NULL, thread_fun2, NULL);

        if(0 != ret)
        {
                perror("thread_create.");
                exit(1);
        }

        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);

        return 0;
}

void *thread_fun1(void *arg)
{
        while(1)
        {
                sem_wait(&sem_id);
                printf("thread1.\n");
        }
}

void *thread_fun2(void *arg)
{
        while(1)
        {
                sem_post(&sem_id);
                printf("thread2.\n");
                sleep(3);
        }
}

​ 运行结果:

image-20221213223608112

图2 运行结果

​ 程序运行开始,几乎同时打印出了thread1.与thread2.。然后每隔3秒钟同时打印出字符串。

10.8 命名信号量

​ 上面的无名信号量用于父子进程之间,而两个具有非亲缘关系进程之间同步使用的是命名信号量。命名信号量创建使用的是sem_open()函数。


NAME
       sem_open - initialize and open a named semaphore

SYNOPSIS
       #include <fcntl.h>           /* For O_* constants */
       #include <sys/stat.h>        /* For mode constants */
       #include <semaphore.h>

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

       Link with -pthread.

​ 查看该函数的编程手册可知,sem_open()函数用来初始化并且打开一个命名信号量。使用时需要包含头文件fcntl.h与sys/stat.h与semaphore.h。函数原型如上图所示。编译时需要跟上-pthread。

ESCRIPTION
       sem_open()  creates  a  new POSIX semaphore or opens an existing semaphore.  The semaphore is identified by name.  For details of the construction of name, see sem_over‐
       view(7).

       The oflag argument specifies flags that control the operation of the call.  (Definitions of the flags values can be obtained by  including  <fcntl.h>.)   If  O_CREAT  is
       specified  in  oflag,  then  the  semaphore is created if it does not already exist.  The owner (user ID) of the semaphore is set to the effective user ID of the calling
       process.  The group ownership (group ID) is set to the effective group ID of the calling process.  If both O_CREAT and O_EXCL are specified in oflag, then  an  error  is
       returned if a semaphore with the given name already exists.

       If  O_CREAT is specified in oflag, then two additional arguments must be supplied.  The mode argument specifies the permissions to be placed on the new semaphore, as for
       open(2).  (Symbolic definitions for the permissions bits can be obtained by including <sys/stat.h>.)  The permissions settings are  masked  against  the  process  umask.
       Both  read and write permission should be granted to each class of user that will access the semaphore.  The value argument specifies the initial value for the new sema‐
       phore.  If O_CREAT is specified, and a semaphore with the given name already exists, then mode and value are ignored.

​ 调用sem_open()创建了一个新的信号量,或者打开已经存在的这个信号量。而这个信号量的标识符就是name这个参数,是一个字符串。参数oflag是一个位掩码,可以写入O_CREAT、 O_EXCL。如果oflag指定O_CREAT,并且指定name的信号量不存在,则创建一个新的信号量。如果oflag同时指定O_CREAT与O_EXCL,并且指定name的信号量已经存在,则sem_open()失败。

​ 如果sem_open()被用来打开一个已经存在的信号量,则调用只需要两个参数。(sem_t *sem_open(const char *name, int oflag);)如果oflag指定了O_CREAT,则还需要mode与value参数。mode也是一个位掩码(可以理解为宏定义在某个头文件)决定了新信号量的权限,只读、只写还是可读可写。一般来说,创建信号量后,后面会用到sem_wait()与sem_post(),需要开启读写权限。value是一个无符号整数,指定了信号量的初值。

​ 不管是创建一个新的信号量还是打开一个已经存在的信号量,sem_open()都会返回一个sem_t的指针,后续操作都是通过这个指针。调用失败则返回SEM_FAILED。

10.9 非亲缘进程之间通过命名信号量同步

​ 创建两个无关进程1和2,在进程1中创建一个新的命名信号量,name参数填入:“NAMED_SEM”。在while(1)中循环等待信号量,并且打印i++。在进程2中,打开之前创建的命名信号量,每隔1秒钟在while(1)中发布一个信号量并且打印i++。

​ named_sem1.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

int main(void)
{
        int i = 0;
        sem_t *sem_id = NULL;

        sem_id = sem_open("Named_sem", O_CREAT, O_RDWR, 0);

        if(SEM_FAILED == sem_id)
                perror("sem_open.");

        while(1)
        {
                sem_wait(sem_id);
                printf("Process 1, i = %d.\n", i++);
        }

        sem_close(sem_id);

        return 0;
}

​ named_sem2.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>

int main(void)
{
        int i = 0;
        sem_t *sem_id = NULL;

        sem_id = sem_open("Named_sem", O_CREAT);

        while(1)
        {
                sem_post(sem_id);
                printf("Process 2, i = %d.\n", i++);
                sleep(1);
        }

        sem_close(sem_id);

        return 0;
}

​ 运行进程1,由于有名信号量初始值==0,所以进程1被阻塞。运行进程2,进程2中发布信号量,进程1也开始执行打印。最后的效果是,进程2执行,进程1也执行。

image-20221213163759322

图3 运行结果

10.10 总结

​ 信号量用于任务间的同步,它本质是是一个整数被内核管理。具有亲缘关系的进程之间可以使用匿名信号量进行任务同步,非亲缘关系的进程之间使用有名信号量进行同步。
在这里插入图片描述

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

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

相关文章

LINUX提权之计划任务提权篇

前言 今天给大家带来的是计划任务提权&#xff0c;说起定时任务对于linux很熟悉的小伙伴一定不会陌生&#xff0c;但你有没有想过可以通过定时任务来进行权限提升的操作&#xff0c;本文会根据该知识点进行展开&#xff0c;同时给大家介绍一个用于探测漏洞的工具使用方法&…

线程通信:生产者消费者问题

问题 1.生产者&#xff08;Producer&#xff09;将产品给店员&#xff08;Clerk&#xff09;&#xff0c;而消费者&#xff08;Customer&#xff09;从店员处取走产品&#xff0c;店员一次只能持有固定数量的产品&#xff08;比如&#xff1a;20&#xff09; &#xff0c;如果生…

实验 1 MATLAB 图像处理基础

一、实验目的1. 熟悉启动和退出 MATLAB 的方法。2. 熟悉 MATLAB 命令窗口的组成。3. 掌握 MATLAB 基本绘图函数和图像处理函数的使用。4. 掌握图像内插和灰度图像的集合运算。二、实验例题1. 求下列表达式的值(1) (2) 答&#xff1a;(1)y1exp(2)/2*sin(35*pi/180)y1 2.1191(2)方…

索尼数字人研究:画质超逼真,面部表情与身体动作保持协调

近年来&#xff0c;3D动捕、数字虚拟人等技术受到越来越多关注&#xff0c;它不仅可以应用于电影场景&#xff0c;游戏、社交等领域也开始采用。相比于过去高成本、高门槛的全身动捕技术&#xff0c;现在制作基于动捕的虚拟人越来越容易&#xff0c;不需要过高的成本或是专业技…

Linux 可加载内核模块剖析

Linux 就是通常所说的单内核&#xff08;monolithic kernel&#xff09;&#xff0c;即操作系统的大部分功能都被称为内核&#xff0c;并在特权模式下运行。 它与微型内核不同&#xff0c;后者只把基本的功能&#xff08;进程间通信 [IPC]、调度、基本的输入/输出 [I/O] 和内存…

Hudi系列1:Hudi介绍

文章目录一. 什么是Hudi二. 发展历史三. Hudi 功能和特性四. Hudi 基础架构五. 使用公司六. 小结参考:一. 什么是Hudi Apache Hudi&#xff08;发音“hoodie”&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接带到数据湖中。Hudi提供了表&#xff0c…

6.6 工具-ELK安装

目录 6.6.1 Elasticsearch安装 6.6.1.1 安装 6.6.1.1.1 window 6.6.1.1.2 Linux 6.6.1.2 问题 6.6.1.2.1 问题一 6.6.1.2.2 问题二 6.6.2 Logstash安装 6.6.2.1 安装 6.6.2.1.1 window 6.6.2.1.2 Linux 6.6.2.2 问题 6.6.2.2.1 问题一 6.6.3 Kibana 6.6.3.1 安装…

论文投稿指南——中文核心期刊推荐(中国医学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

拿捏几道经典的字符串模拟问题

希望本篇对你有所帮助 我发现这种字符串的问题其实写起来很麻烦&#xff0c;可能思路不难多少都能想到一些&#xff0c;主要就是代码的处理&#xff0c;细节问题。太考验代码编写的能力了。这两天写了好多道字符串&#xff0c;模拟之类的问题&#xff0c;今天就分享分享吧 刚…

算法设计与分析-DP习题

7-1 最小路径和给定一个m行n列的矩阵&#xff0c;从左上角开始每次只能向右或者向下移动&#xff0c;最后到达右下角的位置&#xff0c;路径上的所有数字累加起来作为这条路径的和。求矩阵的最小路径和。输入格式:输入第一行&#xff1a;两个正整数m和n(1<m, n<1000)&…

【C++】非类型模板参数、模板特化、模板的分离编译、模板总结

文章目录一、非类型模板参数二、模板特化1.函数模板特化2.类模板特化三、模板的分离编译四、模板总结一、非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 #define N 10…

Spring_FrameWork_05(AOP)

Spring整合Junit RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(classes SpringConfig.class)加载test运行类和spring配置文件 使用Junit提供的Runwith注解&#xff0c;将Junit原有的运行器替换成spring提供的SpringJUnit4ClassRunner。 这个注解的值就是运…

【计算机视觉】Softmax代码实现、过拟合和欠拟合的表现与解决方法

Softmax原理 Softmax函数用于将分类结果归一化&#xff0c;形成一个概率分布。作用类似于二分类中的Sigmoid函数。 对于一个k维向量z&#xff0c;我们想把这个结果转换为一个k个类别的概率分布p(z)。softmax可以用于实现上述结果&#xff0c;具体计算公式为&#xff1a; 对于k…

程序员不了解这些投简历的巨坑,面试注定一开始就失败!

目录 前言第一阶段&#xff1a;练手第二阶段&#xff1a;冲刺第三阶段&#xff1a;收尾 前言 之前写了两篇文章&#xff0c;给大家介绍了一下如何利用短期的时间&#xff0c;尽可能充分的为面试做准备&#xff1a; 1.《我只是把握好了这3点&#xff0c;1个月后成功拿下大厂…

2023春节祝福系列第一弹(下)(放飞祈福孔明灯,祝福大家身体健康)(附完整源代码及资源免费下载)

2023春节祝福系列第一弹&#xff08;下&#xff09; &#xff08;放飞祈福孔明灯&#xff0c;祝福大家身体健康&#xff09; &#xff08;附完整源代码及资源免费下载&#xff09; 目录 四、画一朵真实的祥云 &#xff08;1&#xff09;、画一个渐变的白色径向渐变背景 &a…

外业调查工具助手,照片采集、精准定位、导航、地图查看

你是不是在外业调查时要背着一堆图纸 是不是一不小心图纸污损或丢失&#xff0c;工作又得重做 是不是经常会出现图纸标注的空间不足 是不是外业采集中要携带一大堆繁琐的仪器 是不是每次收集的数据、照片等在整理的过程中发现工作量巨大 是不是经常会出现采集回来的内容跟…

《MySQL 入门教程》第 36 篇 Python 访问 MySQL

本篇我们介绍如何利用 Python DB API 连接和操作 MySQL 数据库&#xff0c;包括数据的增删改查操作、存储过程调用以及事务处理等。 Python 是一种高级、通用的解释型编程语言&#xff0c;以其优雅、准确、 简单的语言特性&#xff0c;在云计算、Web 开发、自动化运维、数据科…

Spark / Java - atomic.LongAccumulator 与 Spark.util.LongAccumulator 计数使用

目录 一.引言 二.atomic.LongAccumulator 1.构造方法 2.使用方法 3.创建并使用 三.Spark.util.LongAccumulator 1.构造方法 2.使用方法 一.引言 使用 Spark 进行大数据分析或相关操作时&#xff0c;经常需要统计某个步骤或多个步骤的相对耗时或数量&#xff0c;java.u…

Java设计模式-适配器模式Adapter

介绍 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;主的目的是兼容性&#xff0c;让原本 因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)适配器模式属于结构型模式主要分为三类&#xff1a;类适配器模式、…

树莓派自带的python3.9->python3.7

卸载python3.9&#xff1a;sudo apt-get remove python3卸载之后一些包可以使用sudo apt autoremove这个命令删除卸载成功如果出现问题后续再来更新&#xff08;出现问题后后续安装python也会失败&#xff09;&#xff08;先不要安装先看&#xff09;安装python3.7&#xff1a;…