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


    简介

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

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

    1. 基础概念

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

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

    2. 核心操作

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

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

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

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

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

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

      int sem_destroy(sem_t *sem);
      
      • 1

    3. 使用场景

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

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

    4. 注意事项

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

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

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

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

    示例

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

    #include 
    #include 
    #include 
    #include 
    
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在上面的例子中:

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

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

    运行结果如下:

    Produced 0
    Consumed 0
    Produced 1
    Consumed 1
    Produced 2
    Consumed 2
    Produced 3
    Consumed 3
    Produced 4
    Consumed 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

    1. 变量

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

    关于线程和进程:

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

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

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

  • 相关阅读:
    Spring MVC各组件近距离接触--中--03
    156 - Ananagrams (UVA)
    【每日三十六记 —— BGP知识点汇总大全】(第一弹)
    tensorrt: pycuda, onnx, onnxruntime, tensorrt,torch-tensorrt 安装
    linux oracle 2022年10月份补丁集:11.2.0.4.221018 PSU补丁包已发布,包含 database,ojvm和GI
    Cypress 与 Selenium WebDriver
    nodejs如何删除指定文件夹的图片
    两天完成牛客网sql必知必会50题(附链接)
    C++强制类型转换
    听说,你想做大模型时代的应用层创业!
  • 原文地址:https://blog.csdn.net/weixin_43844521/article/details/133872807