Linux基础知识点(九-POSIX信号量)

news2024/9/23 19:19:29

目录

一、基本概念

二、有名信号量

三、无名信号量


一、基本概念

信号量(Semaphore)是一种实现进程/线程间通信的机制,可以实现进程/线程之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的进程/线程来访问临界资源。在多进程/线程系统中,各进程/线程之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

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

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

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

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

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

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

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

二、有名信号量

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

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

有名信号量使用到的函数接口

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);
  • sem_open()函数用于打开/创建一个有名信号量,它的参数说明如下:

    • name:打开或者创建信号量的名字。

    • oflag:当指定的文件不存在时,可以指定 O_CREATE 或者 O_EXEL进行创建操作, 如果指定为0,后两个参数可省略,否则后面两个参数需要带上。

    • mode:数字表示的文件读写权限,如果信号量已经存在,本参数会被忽略。

    • value:信号量初始的值,这个参数只有在新创建的时候才需要设置,如果信号量已经存在,本参数会被忽略。

    • 返回值:返回值是一个sem_t类型的指针,它指向已经创建/打开的信号量, 后续的函数都通过改信号量指针去访问对应的信号量。

  • sem_wait()函数是等待(获取)信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0, 则进程/线程阻塞。相当于P操作。成功返回0,失败返回-1。

  • sem_trywait()函数也是等待信号量,如果指定信号量的计数器为0,那么直接返回EAGAIN错误,而不是阻塞等待。

  • sem_post()函数是释放信号量,让信号量的值加1,相当于V操作。成功返回0,失败返回-1。

  • sem_close()函数用于关闭一个信号量,这表示当前进程/线程取消对信号量的使用,它的作用仅在当前进程/线程, 其他进程/线程依然可以使用该信号量,同时当进程结束的时候,无论是正常退出还是信号中断退出的进程, 内核都会主动调用该函数去关闭进程使用的信号量,即使从此以后都没有其他进程/线程再使用这个信号量了, 内核也会维持这个信号量。

  • sem_unlink()函数就是主动删除一个信号量,直接删除指定名字的信号量文件。

#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;
}

 

  • 示例代码由于信号量的控制,运行后得到的结果是:进程A连续打印0,1,2三条语句, 而进程B在A释放信号量后,B连续打印0,1,2三条语句。

  • 假如注释掉示例代码所有跟信号量相关的操作(保留for循环里的sleep)那么由于sleep的存在,运行后得到的结果是:进程A打印0后进入睡眠释放CPU,进程B打印0后进入睡眠释放CPU;进程A打印1、进程B打印1… 即这两个进程轮流执行,轮流打印,如下图所示。

 

三、无名信号量

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

 无名信号量使用到的函数接口:

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);
  • sem_init():初始化信号量。

    • 其中sem是要初始化的信号量,不要对已初始化的信号量再做sem_init操作,会发生不可预知的问题。

    • pshared:表示此信号量是在进程间共享还是线程间共享,由于目前Linux 还没有实现进程间共享无名信号量, 所以这个值只能够取0,表示这个信号量是当前进程的局部信号量。

    • value:信号量的初始值。

    • 返回值:成功返回0,失败返回-1。

  • sem_destroy():销毁信号量,其中sem是要销毁的信号量。只有用sem_init初始化的信号量才能用sem_destroy()函数销毁。 成功返回0,失败返回-1。

  • sem_wait()、sem_trywait()、sem_post()等函数与有名信号量的使用是一样的。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define THREAD_NUMBER 3 /* 线程数 */
#define REPEAT_NUMBER 2 /* 每个线程中的小任务数 */
sem_t sem[THREAD_NUMBER]; /* 保存信号量 */

/*线程函数*/
void *thread_func(void *arg)
{
    int num = (int)arg;
    int count = 0;
    /* 等待信号量,进行 P 操作 */
    sem_wait(&sem[num]);
    printf("Thread %d is starting\n", num);
    for (int count = 1; count < REPEAT_NUMBER; count++)
    {
        printf("\tThread %d: Task %d \n",num, count);
        sleep(1);
    }
    printf("Thread %d finished\n", num);
    /*退出线程*/
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t thread[THREAD_NUMBER];  //保存线程id
    int i = 0, res;
    void * thread_ret;
    /*创建三个线程,三个信号量*/
    for (i = 0; i < THREAD_NUMBER; i++)
    {
        /*创建信号量,初始信号量值为0*/
        sem_init(&sem[i], 0, 0);
        /*创建线程*/
        res = pthread_create(&thread[i], NULL, thread_func, (void*)i);
        if (res != 0)
        {
            printf("Create thread %d failed\n", i);
            exit(res);
        }
    }
    printf("Create treads success\n Waiting for threads to finish...\n");
    /*按顺序释放信号量 V操作*/
    for (i = 0; i<THREAD_NUMBER ; i++)
    {
        /* 进行 V 操作 */
        sem_post(&sem[i]);
        /*等待线程执行完毕*/
        res = pthread_join(thread[i], &thread_ret);
        if (!res)
        {
            printf("Thread %d joined\n", i);
        }
        else
        {
            printf("Thread %d join failed\n", i);
        }
    }
    for (i = 0; i < THREAD_NUMBER; i++)
    {
        /* 删除信号量 */
        sem_destroy(&sem[i]);
    }
    return 0;
}

实例代码在主线程的控制下,它所创建的线程ABC按照释放信号量的次序执行,而且即使上一线程有释放CPU的操作,下一个线程也不会得到CPU的光顾, 因为它未等到自己的信号量。从而在控制下不会出现ACBBAC之类的乱序操作。

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

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

相关文章

洛谷 P1217 [USACO1.5] 回文质数 Prime Palindromes 刷题笔记

P1217 [USACO1.5] 回文质数 Prime Palindromes - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路 直接枚举 减枝优化判断 优化1 只有偶数才会是质数 优化2 回文数的判断次数要优于检查素数 先判断是否为回文数再检查是否为质数 if( hw(i)&&isprime(i)) 这里…

前端根据URL地址实现下载(txt,图片,word,xlsx,ppt)

前端根据URL地址实现下载&#xff08;txt&#xff0c;图片&#xff0c;word&#xff0c;xlsx&#xff0c;ppt&#xff09; 一、对于txt,图片类的二、对于word&#xff0c;xlsx&#xff0c;ppt类的1.a标签可以实现下载2. window.open&#xff08;&#xff09; 一、对于txt,图片类…

Dijkstra算法——邻接矩阵实现+路径记录

本文是在下面这篇文章的基础上做了一些补充&#xff0c;增加了路径记录的功能。具体Dijkstra的实现过程可以参考下面的这篇文章。 [jarvan&#xff1a;Dijkstra算法详解 通俗易懂](Dijkstra算法详解 通俗易懂 - jarvan的文章 - 知乎 https://zhuanlan.zhihu.com/p/338414118) …

一方水土,一方气运

峰民风水悟语&#xff1a;“地灵人杰”&#xff0c;一方好水土&#xff0c;养育一方好人才。风水&#xff0c;就是一个地方的山水之气&#xff0c;会影响一个地方的人。正所谓&#xff1a;“山清水秀出美人&#xff0c;穷山恶水出刁民”就是这个理。 古人认为环境的能量磁场能控…

oracle19c容器数据库rman备份特性-----性能优化(三)

目录 冗余备份片 1.备份的时候指定 2.rman配置中设定 归档备份&#xff08;将备份集保留&#xff09; 二级备份&#xff08;将备份文件保留&#xff09; 1.备份闪回恢复区的恢复文件 2.备份所有恢复文件 recovery catalog database 1.創建recovery catalog 2.创建VPC…

cocos creator 如何绑定参数到编辑器

很多cocos creator同学不知道如何绑定组件属性到编辑器上&#xff0c;今天我们来教大家如何绑定 1: 基本数据属性绑定到编辑器 这个非常简单&#xff0c;模板是属性名字: 默认的值; Is_debug: false, speed: 100, 2: 系统组件类型与节点绑定到编辑器 属性名字: { type: 组件…

少儿编程 中国电子学会图形化编程2022年9月等级考试Scratch二级真题解析(选择题、判断题)

一、单选题(共25题&#xff0c;每题2分&#xff0c;共50分) 一、单选题(共25题&#xff0c;共50分) 1.数列&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;6&#xff0c;9&#xff0c;13&#xff0c;19&#xff0c;28&#xff0c;...的下一项是多少&#…

竞赛保研 基于深度学习的人脸性别年龄识别 - 图像识别 opencv

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计…

第二百五十四回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何给图片添加阴影"相关的内容&#xff0c;本章回中将介绍自定义Radio组件.闲话休提&#xff0c;让我们一起Talk Flutter吧…

Spark 初级编程实践

什么是Spark? Spark是一个快速、通用、可扩展的大数据处理引擎,最初由加州大学伯克利分校的AMPLab开发。它提供了高级API,用于在大规模数据集上执行并行处理。Spark支持多种编程语言,包括Java、Scala、Python和R,因此被广泛应用于大数据分析和机器学习等领域。 一、目的 …

认识Linux指令之 “ 重定向” 符号

01.echo命令 在Linux中&#xff0c;我们可以使用echo命令打印 02. > 输出重定向 在111文件夹中我们只有dir文件夹和file.txt文件 用 echo > &#xff08;输出重定向&#xff09;我们可以将内容输入对应的文件中 也可以直接重定向 > >的作用 创建文件&#xff08…

【MATLAB】小波_LSTM神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 小波-LSTM神经网络时序预测算法是一种结合了小波变换和长短期记忆神经网络&#xff08;LSTM&#xff09;的时间序列预测方法。 小波变换是一种信号处理方法&#xff0c;能够将信号分解为…

.NET国产化改造探索(五)、结合Nginx并确保.NET应用程序自动启动

随着时代的发展以及近年来信创工作和…废话就不多说了&#xff0c;这个系列就是为.NET遇到国产化需求的一个闭坑系列。接下来&#xff0c;看操作。 上一篇介绍了如何在银河麒麟操作系统上安装Nginx&#xff0c;这篇文章详细介绍下在银河麒麟操作系统上&#xff0c;使用Nginx.N…

vue3 封装一个Tooltip 文字提示组件

效果图 默认展示icon图标&#xff0c;悬浮展示文字 如果slot有内容则展示对应内容 实现 用的是El-Tooltip组件 Element - The worlds most popular Vue UI framework 组件代码 <script setup lang"ts"> import { Icon } from /components/Icon import { ElTo…

《剑指offer》专项突破

第一章:整数 面试题1:整数除法 题目 输入两个int型整数,求它们除法的商,要求不得使用乘号’*‘、除号’/‘以及求余符号’%。当发生溢出时返回最大的整数值。假设除数不为0。例如,输入15和2,输出15/2的结果,即7。 参考代码 public int divide(int dividend, int di…

数字孪生与数据可视化大屏

什么是数字孪生 数字孪生技术是一种在现实世界中模拟虚拟世界的技术,它可以将物理世界中的各种事物、过程、行为等转化为虚拟世界中的数据模型,从而实现虚拟世界与现实世界的互动。数字孪生技术可以应用于能源管理、建筑能耗评估、设备全生命周期管理等领域,它可以帮助企业…

计算机体系结构流水线学习记录

一、知识点汇总 1.理想情况下&#xff0c;流水线能够实现 n 倍的吞吐率加速比&#xff08;n为流水线深度&#xff09;&#xff0c;但是流水线深度并非越大越好&#xff0c;因为流水线的深度会影响到性能和功耗之间的平衡。 2.RISC&#xff1a;Reduced Instruction Set Comput…

WPF真入门教程26--项目案例--欧姆龙PLC通讯工具

1、案例介绍 前面已经完成了25篇的文章介绍&#xff0c;概括起来就是从0开始&#xff0c;一步步熟悉了wpf的概念&#xff0c;UI布局控件&#xff0c;资源样式文件的使用&#xff0c;MVVM模式介绍&#xff0c;命令Command等内容&#xff0c;这节来完成一个实际的项目开发&#…

第57、58颗北斗导航卫星发射成功

第57、58颗北斗导航卫星发射成功&#xff01; 12月26日11时26分&#xff0c;我国在西昌卫星发射中心用长征三号乙运载火箭与远征一号上面级&#xff0c;成功发射第57、58颗北斗导航卫星。 这组卫星属中圆地球轨道卫星&#xff08;MEO卫星&#xff09;&#xff0c;是我国北斗三…

自动计算薪资-全优学堂

功能说明 全优学堂薪资模块则根据基础薪资、历史上课情况、课程销售情况自动计算员工薪资&#xff0c;帮助您更好地进行成本管理。系统根据教职工的排班情况、课时数和提成规则&#xff0c;自动计算教职工的薪资&#xff0c;大大减轻工资管理负担。 #1. 基础薪资配置 设置本…