Linux多线程编程- 无名信号量

简介

无名信号量(在 POSIX 环境下通常指 sem_t 类型的信号量)是用于同步和互斥的原语,它允许线程和进程按照预期的顺序执行,并确保对共享资源的安全访问。无名信号量与命名信号量的主要区别在于它们的可见性和生命周期。无名信号量通常用于一个进程内的线程间同步,而命名信号量用于多个进程间的同步。

以下是无名信号量的详细介绍:

1. 基础概念

  • 信号量的值:信号量是一个非负整数,通常代表可用的资源数量。例如,信号量值为2意味着有2个资源可用。

  • 操作 :主要有两种基本操作 - wait(或 downP)和 post(或 upV)。

2. 核心操作

  • sem_init:用于初始化信号量。需要提供信号量变量的地址、一个标志(指示信号量是否应在多个进程之间共享)以及信号量的初始值。

    c 复制代码
    int sem_init(sem_t *sem, int pshared, unsigned int value);

    其中,pshared 通常设为0,表示此信号量只用于当前进程的线程之间的同步。

  • sem_wait:如果信号量的值大于零,它将减少信号量的值并继续。如果信号量的值为0,调用此操作的线程将被阻塞,直到信号量的值变为正数。

    c 复制代码
    int sem_wait(sem_t *sem);
  • sem_post:增加信号量的值。如果其他线程正在等待此信号量,一个或多个等待线程可能被唤醒。

    c 复制代码
    int sem_post(sem_t *sem);
  • sem_destroy:销毁信号量,释放与其关联的任何资源。

    c 复制代码
    int sem_destroy(sem_t *sem);

3. 使用场景

  • 互斥访问:当多个线程需要访问共享资源,但我们希望一次只有一个线程可以访问时,可以使用信号量。例如,访问一个共享文件或更新一个共享数据结构。

  • 条件同步:例如,一个线程生产数据,另一个线程消费数据。消费者线程可能需要等待,直到生产者线程生产了足够的数据。

4. 注意事项

  • 虽然无名信号量通常用于线程间同步,但在某些平台和实现中,它们也可以用于进程间同步,只要这些进程共享同一个信号量变量。

  • 使用 sem_destroy 之前,确保没有线程等待该信号量。否则,行为可能是未定义的。

  • 与所有同步原语一样,使用信号量需要谨慎,以避免死锁和竞态条件。

总的来说,无名信号量是一种非常有用的同步工具,它提供了一种简单、有效的方法来协调线程的行为和确保对共享资源的安全访问。

示例

以下是一个使用 POSIX 无名信号量进行线程间同步的例子。在这个例子中,我们有两个线程:生产者和消费者。生产者线程生成数据,消费者线程消费它。我们使用信号量来确保生产者产生数据后消费者才开始消费。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem_producer, sem_consumer;

#define DATA_SIZE 5
int buffer[DATA_SIZE];
int index = 0;

void* producer(void* arg) {
    for (int i = 0; i < DATA_SIZE; i++) {
        sem_wait(&sem_producer);  // Wait until there is a spot available.
        buffer[index++] = i;  // Produce data.
        printf("Produced %d\n", i);
        sem_post(&sem_consumer);  // Signal that data has been produced.
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < DATA_SIZE; i++) {
        sem_wait(&sem_consumer);  // Wait until data is available.
        int data = buffer[--index];  // Consume data.
        printf("Consumed %d\n", data);
        sem_post(&sem_producer);  // Signal that a spot is free.
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;

    // Initialize semaphores
    sem_init(&sem_producer, 0, DATA_SIZE);  // Initially, DATA_SIZE spots are available.
    sem_init(&sem_consumer, 0, 0);  // Initially, no data is available to consume.

    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, consumer, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    sem_destroy(&sem_producer);
    sem_destroy(&sem_consumer);

    return 0;
}

在上面的例子中:

  1. sem_producer 信号量表示可用的缓冲区槽位数量。开始时,所有槽位都是可用的。
  2. sem_consumer 信号量表示可供消费的数据数量。开始时没有数据可供消费。

生产者每次生产一个数据项前都会等待一个可用的槽位,消费者在每次消费前都会等待可供消费的数据。

运行结果如下:

bash 复制代码
Produced 0
Consumed 0
Produced 1
Consumed 1
Produced 2
Consumed 2
Produced 3
Consumed 3
Produced 4
Consumed 4

(从另一个视角看这个程序)

以下是生产者和消费者线程共享的资源:

  1. 变量

    • buffer[]:这是一个整数数组,用于存储生产者产生的数据和消费者消费的数据。
    • index:这是一个整数,表示buffer[]中的下一个可用位置或要被消费的数据位置。
    • sem_producersem_consumer:这是我们用于同步的无名信号量。一个表示有多少空的槽位可用来存储数据,另一个表示有多少数据可供消费。
  2. 无名信号量 :我们使用sem_init初始化了两个无名信号量。由于这两个信号量是在主进程的地址空间内初始化的,它们可以被该进程内的所有线程(在这里,是生产者和消费者线程)访问和操作。

关于线程和进程:

  • 进程 :在这个程序中,主函数main()运行在主进程中。所有的全局变量、函数、和在main()函数内定义的局部变量都在这个进程的地址空间内。

  • 线程 :我们使用pthread_create()创建了两个线程。这两个线程(生产者和消费者)都在上述的主进程内运行。因此,它们共享上述的主进程的地址空间,这也就是为什么它们可以访问和操作同样的变量和无名信号量。

总结:这个程序中只有一个进程。在这个进程内,我们创建了两个线程,它们共享同一块地址空间。这也是为什么无名信号量特别适合于线程间的同步:因为所有线程都可以直接访问和操作进程内的同一个无名信号量。

相关推荐
tokepson11 分钟前
Mysql下载部署方法备份(Windows/Linux)
linux·服务器·windows·mysql
zz_nj2 小时前
工作的环境
linux·运维·服务器
极客先躯3 小时前
如何自动提取Git指定时间段的修改文件?Win/Linux双平台解决方案
linux·git·elasticsearch
suijishengchengde4 小时前
****LINUX时间同步配置*****
linux·运维
qiuqyue4 小时前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
linux·运维·网络
切糕师学AI4 小时前
Linux 操作系统简介
linux
南烟斋..5 小时前
GDB调试核心指南
linux·服务器
爱跑马的程序员5 小时前
Linux 如何查看文件夹的大小(du、df、ls、find)
linux·运维·ubuntu
oMcLin7 小时前
如何在 Ubuntu 22.04 LTS 上部署并优化 Magento 电商平台,提升高并发请求的响应速度与稳定性?
linux·运维·ubuntu
Qinti_mm7 小时前
Linux io_uring:高性能异步I/O革命
linux·i/o·io_uring