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

Linux匿名信号量详细介绍

回顾POSIX命名信号,互斥锁,读写锁

在这篇文章之前我们已经学习过了POSIX命名信号,互斥锁,读写锁等多种同步互斥的机制。其中POSIX命名信号是用在进程间的,互斥锁和读写锁是用在线程间的。

对于posix命名信号量来说:

  • 命名信号量不需要手动定义变量,它由系统在文件系统中(通常是 /dev/shm 目录)创建一个特殊的文件。
  • 通过 sem_open() 函数指定一个字符串名称来创建或打开一个命名信号量。
  • 用于进程之间的同步。
  • sem_open() 函数在创建时会进行初始化。
  • 使用 sem_wait()sem_post() 函数进行等待和释放操作。
  • 值得注意的是,命名信号量的生命周期和进程不同步,POSIX命名信号量的生命由操作系统管控,所以退出进程命名信号量不会自动销毁,需要我们编写代码手动销毁,不然占用空间且容易出现意想不到的情况。

对于互斥锁读写锁来说:

互斥锁(pthread_mutex_t)和读写锁(pthread_rwlock_t)需要我们手动在线程间的共享内存区域手动定义变量,以便多个线程可以访问。

定义了相关变量后,我们可以使用系统为我们提供的函数接口对其进行初始化,申请锁,释放锁,销毁锁等操作。

主要用于线程间的互斥访问共享资源。读写锁在读多写少的场景下,可以提高并发性能。

互斥锁和读写锁的生命周期与进程同步,在进程结束自动销毁,不需要我们进行手动释放。

posix匿名信号量简单认识

对于posix匿名信号量来说:

虽然名字和posix命名信号量有些像,但是功能和其有很大差别!!!posix匿名信号量主要用在线程间的同步,需要我们手动在线程间的共享内存区域中手动定义相关变量生命周期与所属进程同步使用流程和互斥锁,读写锁差不多

互斥锁和读写锁专注于实现线程间互斥和读写控制,但是不能实现线程间执行顺序的同步

posix匿名信号量提供了更广泛的同步能力,主要用于实现线程间执行顺序的同步

POSIX匿名信号量常见函数

  • sem_init(sem_t *sem, int pshared, unsigned int value)
    用于初始化匿名信号量。如果 pshared 设置为 0,则信号量在线程间共享(适用于同一进程的多线程同步)。value 指定信号量的初始值。例如:

    sem_init(&sem, 0, 1); // 线程间共享,初始值为 1
  • sem_destroy(sem_t *sem)
    用于销毁信号量,释放相关资源。在不再需要信号量时调用,例如:

    sem_destroy(&sem);
  • sem_wait(sem_t *sem)
    用于等待信号量。当信号量的值大于 0 时,sem_wait 会减少其值,并继续执行。如果信号量的值为 0,线程将阻塞,直到其他线程调用 sem_post 释放信号量。例如:

    sem_wait(&sem); // 如果信号量值为 0,则阻塞等待
  • sem_trywait(sem_t *sem)
    尝试获取信号量,但如果信号量当前值为 0,不会阻塞,而是直接返回 -1 并设置 errno,适用于不想等待的场景。例如:

    if (sem_trywait(&sem) == 0) { 
    // 成功获取信号量 
    }
    else {
    // 失败,信号量值为 0 
    }
  • sem_post(sem_t *sem)
    用于释放信号量,使其他等待的线程可以继续执行。每调用一次 sem_post,信号量的值增加 1。例如:

    sem_post(&sem); // 释放信号量,唤醒等待的线程
  • sem_getvalue(sem_t *sem, int *sval)
    获取信号量的当前值并存储sval 指向的变量中。例如:

    int value; sem_getvalue(&sem, &value); 
    printf("Current semaphore value: %d\n", value);

POSIX匿名信号量使用流程

1.声明信号量

在全局或局部声明一个 sem_t 类型的变量:

sem_t sem;

2.初始化信号量

使用 sem_init 进行初始化,第二个参数设为 0 以支持线程间共享:

sem_init(&sem, 0, initial_value);

initial_value 设定信号量的初始值,决定了有多少个线程可以立即进入临界区。

3.线程操作信号量

等待信号量(P 操作):线程调用 sem_wait,如果信号量值大于 0,则减少其值并继续执行;否则阻塞等待:

sem_wait(&sem);

释放信号量(V 操作):线程完成任务后调用 sem_post,增加信号量值,允许其他线程继续:

sem_post(&sem);

线程执行任务 在获取信号量后,线程可以执行需要同步的任务,如访问共享资源、执行特定操作等。

4.销毁信号量

在程序退出或信号量不再使用时,调用 sem_destroy 释放资源:

sem_destroy(&sem);

这个流程适用于 任意 使用匿名信号量进行线程同步的场景,比如生产者-消费者模式、互斥控制、信号量控制的多线程任务等。如果你有更具体的需求,比如进程间同步,可以考虑使用 具名信号量(sem_open) 或 共享内存 + 信号量。

匿名信号量函数接口详解

sem_init() -初始化信号量

函数作用:初始化一个匿名信号量。

头文件:#include <semaphore.h>

函数原型:int sem_init(sem_t *sem,  int pshared,  unsigned int value);

参数

  • sem:指向 sem_t 类型变量的指针,指向待初始化的信号量。
  • pshared:指定信号量的共享范围。
            如果 pshared 为 0,则信号量只能在同一进程的线程间共享。
            如果 pshared 非 0,则信号量可以在不同进程间共享(需要将信号量放置在共享内存              区)。
    通常匿名信号量只在控制线程之间同步的时候使用,所以这个参数设置为0
  • value:信号量的初始值,必须是非负数。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno。

注意

匿名信号量一般都用于线程之间的同步不用在进程之间。所以匿名信号量一般会定义成静态全局变量或者全局变量,来使得所有线程可见。

sem_wait() - 获取信号量

函数作用:尝试获取信号量。

头文件:#include <semaphore.h>

函数原型:int sem_wait(sem_t *sem);

参数

sem:指向要操作的信号量的指针。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno。

注意

  • 如果信号量的值大于 0,则将其减 1 并立即返回;
  • 如果信号量的值大等于0,调用线程将被阻塞,直到信号量的值大于 0。

sem_trywait() - 非阻塞获取信号量

函数作用:尝试获取信号量,但不会阻塞。

头文件:#include <semaphore.h>

函数原型:int sem_trywait(sem_t *sem);

参数

sem:指向要操作的信号量的指针。

返回值

  • 成功时返回 0。
  • 如果信号量不可用,返回 -1,并设置 errno 为 EAGAIN。
  • 其他错误,返回-1,并设置errno。

注意

  • 如果信号量的值大于 0,则将其减 1 并立即返回 0;
  • 如果信号量的值大等于0,不阻塞,立即返回 -1,并设置 errno 为 EAGAIN。

sem_post() - 释放信号量

函数作用:释放信号量,将其值加 1

头文件:#include <semaphore.h>

函数原型:int sem_post(sem_t *sem);

参数

sem:指向要操作的信号量的指针。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno。

注意

sem_post函数的作用是释放信号量,将其值加 1。如果此时有线程被阻塞在 sem_wait() 函数中,则唤醒其中一个线程。

sem_destroy() - 销毁信号量

函数作用:销毁一个匿名信号量。

头文件:#include <semaphore.h>

函数原型:int sem_destroy(sem_t *sem);

参数

sem:是一个指向要销毁的信号量的指针。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno。

sem_getvalue() - 获取信号量值

函数作用:获取 信号量当前的值,并将其存储在 sval 指向的变量中

头文件:#include <semaphore.h>

函数原型:int sem_getvalue(sem_t *sem, int *sval);

参数

  • sem:指向已初始化的 sem_t 类型的信号量对象。
  • sval:指向 int 类型的变量,用于存储信号量的当前值。

返回值

  • 成功时返回 0,并将信号量的值存入 sval。
  • 失败时返回 -1,并设置 errno

POSIX匿名信号量例子

例子描述:

我们创建两个线程:

  1. 生产者线程:生成数据,并通知消费者。
  2. 消费者线程:等待数据可用后进行消费。

两者通过 匿名信号量 进行同步,确保消费者不会在数据准备好之前执行。

实验预期结果

完整代码

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

#define NUM_ITERATIONS 5  // 生产和消费的次数

sem_t sem;  // 声明匿名信号量
int shared_data = 0;  // 共享数据

// 生产者线程
void* producer(void* arg) {
    for (int i = 0; i < NUM_ITERATIONS; i++) {
        sleep(1);  // 模拟生产过程
        shared_data = i + 1;
        printf("Producer: produced %d\n", shared_data);

        sem_post(&sem);  // 增加信号量,通知消费者
    }
    return NULL;
}

// 消费者线程
void* consumer(void* arg) {
    for (int i = 0; i < NUM_ITERATIONS; i++) {
        sem_wait(&sem);  // 等待信号量
        printf("Consumer: consumed %d\n", shared_data);
    }
    return NULL;
}

int main() {
    pthread_t prod_thread, cons_thread;

    // 初始化匿名信号量,第二个参数=0 代表线程内共享
    if (sem_init(&sem, 0, 0) != 0) {
        perror("sem_init failed");
        exit(EXIT_FAILURE);
    }

    // 创建生产者和消费者线程
    pthread_create(&prod_thread, NULL, producer, NULL);
    pthread_create(&cons_thread, NULL, consumer, NULL);

    // 等待线程完成
    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    // 销毁信号量
    sem_destroy(&sem);
    return 0;
}

代码解释

  • sem_init(&sem, 0, 0);

    • 第一个参数:指向信号量的指针。
    • 第二个参数:设为 0,表示该信号量仅在线程间共享(适用于同一进程的线程同步)。
    • 第三个参数:初始化值 0,表示消费者必须等待生产者生产数据。
  • 生产者 (producer 线程)

    • 生产数据后调用 sem_post(&sem);,增加信号量值,通知消费者数据可用。
  • 消费者 (consumer 线程)

    • sem_wait(&sem);,如果信号量值为 0,消费者线程阻塞等待,直到生产者 sem_post 增加信号量值后,才能继续执行。
  • 销毁信号量 (sem_destroy(&sem);)

    • 释放信号量资源,避免内存泄漏。

相关文章:

  • 千里科技亮相吉利AI智能科技发布会,共启“AI+车”新纪元
  • JavaWeb后端基础(4)
  • 牙齿缺陷分割数据集labelme格式2495张4类别
  • QT实现单个控制点在曲线上的贝塞尔曲线
  • 数据结构入门篇——什么是数据结构。
  • C#进阶指南
  • 在 UniApp 中实现中间凸起 TabBar 的完整指南
  • No manual entry for printf in section 3
  • 第四十五:创建一个vue 的程序
  • MyBatisPlus搭建教程
  • 国产免费AI的IDE-TRAE
  • iOS安全和逆向系列教程 第4篇:搭建iOS逆向开发环境 (下) - 越狱设备与高级工具配置
  • Kali换源-pikachu
  • Docker 深度解析:适合零基础用户的详解
  • iOS安全和逆向系列教程 第5篇 iOS基础开发知识速览 - 理解你要逆向的目标
  • 【开源项目-AI研发】ai-engineer-toolkit
  • Android Studio安装教程
  • IvorySQL v4 逻辑复制槽同步功能解析:高可用场景下的数据连续性保障
  • 驱动开发系列40 - Linux 显卡KMD驱动代码分析(一) - 设备初始化过程
  • Xcode 无限循环闪退解决方案
  • wordpress 站点收录/排名优化价格
  • 网站建设手机源码/企业官网建站
  • 默认网站停止/seo优化服务是什么
  • 外贸网站建设哪里做得好/做小程序要多少钱
  • 山东网站优化公司/广告营销是做什么的
  • 最新设计网站大全/代写