当前位置: 首页 > news >正文

Linux 基础入门操作 第九章 进程之间通讯信号量 2

1 信号量

信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号量有以下两种类型:

二值信号量:最简单的信号量形式,信号灯的值只能取 0 或 1,类似于互斥锁。
计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)

信号量只能进行两种操作等待和发送信号,即 P(sv)和 V(sv),他们的行为是这样的:
P(sv):如果 sv 的值大于零,就给它减 1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而挂起,就给它加 1.

POSIX 信号量 目前编程中比较常用的模块。

2 POSIX 信号量

2.1 特点

它有两个优势:

  1. 现代标准:遵循 POSIX 标准(<semaphore.h>)。
  2. 轻量级:设计更简洁,支持线程和进程间同步。

POSIX 信号量有两种类型:

  1. 命名信号量(Named Semaphore):通过文件名标识,可用于无亲缘关系的进程。
  2. 匿名信号量(Unnamed Semaphore):存在于共享内存中,通常用于线程或多进程(需共享内存)。

2.2 涉及到函数

POSIX 信号量 的主要函数如下所示, 将在下面的章节中进行具体介绍。
在这里插入图片描述

3 命名信号量

命名信号量可以在不同进程之间共享,可以没有亲缘关系的进程李米娜进行通讯。

3.1 相关函数

在这里插入图片描述

3.1.1 sem_open() 函数

sem_open() 是 POSIX 信号量 API 中的一个函数,用于创建或打开一个命名信号量。

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

参数如下:

name:信号量的名字。必须以 / 开头,例如 /my_sem。
实际文件存储在 /dev/shm/sem.mysem。
名称长度限制(通常 NAME_MAX-4,即 251 字符)
oflag:打开标志,可以是以下值的组合:
O_CREAT:如果信号量不存在,则创建它。
O_EXCL:与 O_CREAT 一起使用,如果信号量已存在,则返回错误。
mode:信号量的权限(类似于文件权限),例如 0666。
value:信号量的初始值(仅在创建信号量时有效)。 一般为1

返回值
成功时返回指向信号量的指针。
失败时返回 SEM_FAILED,并设置 errno。

3.1.2 sem_close() 和 sem_unlink() - 清理资源

sem_unlink 是 POSIX 信号量(Named Semaphore) 的清理函数,用于 从文件系统中删除命名信号量的标识。它不会立即销毁信号量,而是标记删除,当所有引用该信号量的进程关闭后,内核会自动释放资源。

先要关闭信号量,删除信号量。

#include <semaphore.h>
int sem_close(sem_t *sem);          // 关闭信号量(进程内)
int sem_unlink(const char *name);   // 删除信号量(文件系统)

删除信号量的函数介绍如下:

#include <semaphore.h>
int sem_unlink(const char *name);

name 命名信号量的文件系统路径(如 “/mysem”),必须以 / 开头。
返回值
成功:返回 0。
失败:返回 -1 并设置 errno(如 ENOENT 表示信号量不存在)

3.1.3 sem_wait() 和 sem_post() - P/V 操作

int sem_wait(sem_t *sem);   // P操作(阻塞)
int sem_post(sem_t *sem);   // V操作(释放)

3.2 命名信号量的使用示例

父子进程同时打印数据,通过互斥量保护打印数据。

#include <stdio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>

int main() {
    const char *sem_name = "/my_sem";  // 信号量名称
    sem_t *sem = sem_open(sem_name, O_CREAT, 0644, 1);  // 创建信号量,初始值为 1
    if (sem == SEM_FAILED) {
        perror("sem_open");
        return 1;
    }
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        sem_wait(sem);  // 等待信号量(P 操作)
        printf("Child process acquired the semaphore\n");
        sleep(2);  // 模拟临界区操作
        printf("Child process releasing the semaphore\n");
        sem_post(sem);  // 释放信号量(V 操作)
    } else if (pid > 0) {
        // 父进程
        sem_wait(sem);  // 等待信号量(P 操作)
        printf("Parent process acquired the semaphore\n");
        sleep(2);  // 模拟临界区操作
        printf("Parent process releasing the semaphore\n");
        sem_post(sem);  // 释放信号量(V 操作)
        wait(NULL);  // 等待子进程结束
        sem_close(sem);  // 关闭信号量
        sem_unlink(sem_name);  // 删除信号量
    } else {
        perror("fork");
        return 1;
    }
    return 0;
}

4 未命名信号量

未命名信号量(Unnamed Semaphore)是 POSIX 信号量 的一种,仅通过内存地址访问(不依赖文件系统路径),适用于 线程间同步 或 共享内存的进程间同步。

4.1 核心函数

常见函数如下所示:
在这里插入图片描述

4.1.1 sem_init 函数

创建信号量的, 一般初始值为1。

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

0:信号量在线程间共享。
1:信号量在进程间共享(需放在共享内存中)。
value:信号量初始值(如 1 表示二进制信号量)。

4.1.2 sem_destroy()

匿名信号量不会自动释放资源,需显式销毁。

sem_destroy(sem);  // 销毁信号量

4.2 参考案例

4.2.1 线程同步

这里创建两个线程。 对线程的资源进行保护。关于线程的介绍,可以参考这篇文章。

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

sem_t sem;  // 未命名信号量

void* thread_func(void* arg) {
    sem_wait(&sem);  // 等待信号量(P 操作)
    printf("Thread acquired the semaphore\n");
    sleep(2);  // 模拟临界区操作
    printf("Thread releasing the semaphore\n");
    sem_post(&sem);  // 释放信号量(V 操作)
    return NULL;
}

int main() {
    sem_init(&sem, 0, 1);  // 初始化未命名信号量,初始值为 1

    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    sem_wait(&sem);  // 等待信号量(P 操作)
    printf("Main thread acquired the semaphore\n");
    sleep(2);  // 模拟临界区操作
    printf("Main thread releasing the semaphore\n");
    sem_post(&sem);  // 释放信号量(V 操作)

    pthread_join(tid, NULL);  // 等待线程结束
    sem_destroy(&sem);  // 销毁信号量

    return 0;
}

4.2.2 进程同步-共享内存

#include <semaphore.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    // 1. 创建共享内存
    int shmid = shmget(IPC_PRIVATE, sizeof(sem_t), 0666);
    sem_t *sem = (sem_t*)shmat(shmid, NULL, 0);

    // 2. 初始化信号量(1表示进程间共享)
    sem_init(sem, 1, 1); // 初始值=1

    if (fork() == 0) {
        // 子进程
        sem_wait(sem);
        printf("Child process in critical section\n");
        sem_post(sem);
        shmdt(sem);
    } else {
        // 父进程
        sem_wait(sem);
        printf("Parent process in critical section\n");
        sem_post(sem);
        wait(NULL);
        sem_destroy(sem);  // 销毁信号量
        shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
    }
    return 0;
}

5 总结

综合前面两个文章,两个信号量特点如下:
在这里插入图片描述

相关文章:

  • 成人在色线视频在线观看免费大全青岛seo推广专员
  • 网站建设考核标准北京网站快速优化排名
  • 网站开发技术考题电子商务网站推广策略
  • 企业网站建设费用计入什么科目百度手机助手app
  • 蒙古文网站建设情况亚马逊查关键词排名工具
  • 微信网站制作企业线下推广方式有哪些
  • Scala(五)
  • 【Anaconda环境绑定指南】3步将自定义环境注入Jupyter Notebook 内核 | 附详细命令与演示
  • [Redis]Redis学习开篇概述
  • Linux 容器环境磁盘空间不足问题及解决方案
  • 最新源支付V7开源1.9.9版
  • 零基础使用AI从0到1开发一个微信小程序
  • KisFlow-Golang流式实时计算案例(四)-KisFlow在消息队列MQ中的应用
  • 讲解机器学习中的 K-均值聚类算法及其优缺点
  • 哈希表+前缀和+滑动窗口高效查找——蓝桥杯例题
  • 【算法】反转单向链表 链表和数组的区分 时间复杂度
  • 野指针成因及避免方法
  • 制造OA系统怎么选?有没有简单易用的?
  • Android Fresco 框架编解码模块源码深度剖析(三)
  • 数据结构与算法面试题精讲)C++版——day4
  • day20 学习笔记
  • PyTorch中的损失函数
  • 【Django】教程-10-ajax请求Demo,结合使用
  • 算法导论(动态规划)——子数组系列
  • 了解Docker容器的常见退出状态码及其含义
  • dify新版本1.1.3的一些问题