Bootstrap

linux 信号量sem 使用示例

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

信号量主要用于进程间使用
信号量:分为 posix 和 systemV 信号量
posix信号量:

sem_open :打开/创建sem
sem_close :关闭sem
sem_unlink :删除sme
sem_post : P操作+1
sem_wait : V操作 -1,函数小于0的时候会阻塞
sem_getvalue :调试使用,存在竞争态,不使用

systemV信号量:

int semget(key_t key, int nsems, int semflg); * 功能:创建或访问一个信号量集。
int semop(int semid, struct sembuf sops, size_t nsops); * 功能:对信号量集执行操作,如增加或减少信号量的值。
int semctl(int semid, int semnum, int cmd, … /
union semun arg */); * 功能:对信号量集执行控制操作,如初始化信号量、获取信号量信息或删除信号量集。
key_t ftok(const char *pathname, int proj_id); * 功能:用于生成一个唯一键值,用于semget()函数中创建或访问信号量集。


提示:以下是本篇文章正文内容,下面案例可供参考

一、信号量是什么?

信号量(Semaphore)是进程间通信(IPC,Inter-Process Communication)机制的一种,主要用于解决进程间的同步和互斥问题。信号量最早由荷兰计算机科学家 Edsger Dijkstra 提出,它是一种软件抽象,可以控制对共享资源的访问,防止多个进程或线程同时访问同一资源,从而避免竞态条件和死锁。

信号量的基本概念包括:

计数器:信号量本质上是一个计数器,用来记录可用资源的数量。计数器的值可以是正数、零或负数。
P操作(Wait操作):当一个进程想要访问共享资源时,它会对信号量执行 P 操作。P 操作会将信号量的值减1。如果信号量的值大于等于0,则进程可以继续执行并访问资源。如果信号量的值小于0,这意味着没有可用资源,进程会被阻塞,直到资源可用。
V操作(Signal操作):当一个进程完成对资源的使用时,它会对信号量执行 V 操作。V 操作会将信号量的值加1。如果在信号量的等待队列中有被阻塞的进程,其中一个会被唤醒,以访问资源。

二、代码示例

1.posix

代码如下(示例):
代码示例:父进程每3秒生产,子进程每1秒消费,生产不足时候子进程阻塞。
gcc posix_sem.c -o posix_sem -lrt -lpthread -g

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/wait.h>

#define SEMAPHORE_PATH "/example_semaphore"

sem_t* init_semaphore(const char* name) {
    int val = 0;
    sem_t* semaphore = sem_open(name, O_CREAT | O_EXCL, 0644, 1); // 信号量创建时值就为1
    if (semaphore == SEM_FAILED) {
        perror("sem_open");
        exit(EXIT_FAILURE);
    }
    printf("sem_open val = %d\n", val);
    return semaphore;
}


void cleanup_semaphore(sem_t* semaphore) {
    if (sem_close(semaphore) == -1) {
        perror("sem_close");
        exit(EXIT_FAILURE);
    }
    if (sem_unlink(SEMAPHORE_PATH) == -1) {
        perror("sem_unlink");
        exit(EXIT_FAILURE);
    }
}


void increment_semaphore(sem_t* semaphore) {
    if (sem_post(semaphore) == -1) {
        perror("sem_post");
        exit(EXIT_FAILURE);
    }
}

void decrement_semaphore(sem_t* semaphore) {
    if (sem_wait(semaphore) == -1) {
        perror("sem_wait");
        exit(EXIT_FAILURE);
    }
}

int main() {
    sem_t* semaphore = init_semaphore(SEMAPHORE_PATH);
    
    printf("semaphore = %d\n", semaphore);
    
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid > 0) { // 父进程 - 生产者
        int status;
    
        for (int i = 0; i < 5; ++i) {
            printf("Producer incremented semaphore.\n");
            increment_semaphore(semaphore);
            sleep(3);
        }
        pid_t child_pid = waitpid(pid, &status, 0);
        
        cleanup_semaphore(semaphore);
    } else { // 子进程 - 消费者
        int val = 0;
        for (int i = 0; i < 5; ++i) {
            printf("Consumer decremented semaphore.\n");
            decrement_semaphore(semaphore);
            sem_getvalue(semaphore,&val);
            printf("val = %d\n", val);
            sleep(1);
        }
    }

    return 0;
}

2.systemV

代码示例:生产者生产 0 1 sem,消费进程 cust 消费0,cust1 消费 1
要特别注意//op.sem_flg = SEM_UNDO; // 如果设置了SEM_UNDO标志,当线程结束时,系统会自动执行一个V操作(即sem_post),以恢复信号量的初始状态。producer先退出时候会导致consumer1异常, SEM_UNDO是系统推荐设置,尽量设置

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>


void producer(int sem_id);
void consumer(int sem_id);
void consumer1(int sem_id);


union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main() {
    int sem_id;
    union semun arg;
    arg.val = 1; // 初始化信号量为1

    key_t key = ftok("/tmp/semaphore_key", 65);
    if (key == (key_t)-1) {  
        perror("ftok failed");
        exit(-1);
    }

/*
    // 创建一个信号量组,有两个信号量
    if ((sem_id = semget(key, 2, 0666 | IPC_CREAT)) == -1) {
        perror("semget");
        exit(1);
    }

    semctl(sem_id, 0, IPC_RMID, 0); // 删除信号量集
    sleep(2);
*/
    //重新创建
    if ((sem_id = semget(/*key*/ 432, 2, 0666 | IPC_CREAT)) == -1) {
        perror("semget");
        exit(1);
    }
    //初始化 0 sem值为1
    if (semctl(sem_id, 0, SETVAL, arg) == -1) {
        perror("semctl");
        exit(1);
    }
    //初始化 1 sem值为1
    if (semctl(sem_id, 1, SETVAL, arg) == -1) {
        perror("semctl");
        exit(1);
    }

    pid_t pid;

    if ((pid = fork()) == 0) { // 子进程1作为消费者
        printf("consumer start\n");
        int i = 5;
        while(i)
        {
            printf("consumer enter i = %d\n", i);
            consumer(sem_id);
            i--;
        }
        printf("consumer end\n");
    } else if ((pid = fork()) == 0) { // 子进程2作为生产者
        int i = 4; 
        while(i)
        {
            printf("producer start i = %d\n", i);
            producer(sem_id);
            i--;
        }
        printf("producer end\n");
        //sleep(10);
    } else if ((pid = fork()) == 0) { // 子进程3作为消费者
        printf("consumer1 start\n");
        int i = 5;
        while(i)
        {
            printf("consumer1 enter i = %d\n", i);
            consumer1(sem_id);
            i--;
        }
        printf("consumer1 end\n");
    }else {
        wait(NULL); // 父进程等待子进程完成
        wait(NULL);
        wait(NULL);
        semctl(sem_id, 0, IPC_RMID, 0); // 删除信号量集
        printf("Semaphore removed.\n");
    }
    
    return 0;
}

void consumer(int sem_id) {
    int ret = -1;
    struct sembuf op;
    op.sem_num = 0;
    op.sem_op = -1; // 减少信号量的值
    op.sem_flg = SEM_UNDO;
    union semun arg;

    while (ret != 0) {
    if (semctl(sem_id, 0, GETVAL, arg) == -1) {
          perror("semctl GETVAL");
          return;
        }

        //printf("consumer Semaphore value is %d\n", arg.val);
        ret = semop(sem_id, &op, 1);
        if (ret == -1) {
            perror("semop");
            exit(1);
        }
        //printf("Consumer decremented semaphore value.\n");
        sleep(2);
    }
}


void consumer1(int sem_id) {
    int ret = -1;
    struct sembuf op;
    op.sem_num = 1;
    op.sem_op = -1; // 减少信号量的值
    op.sem_flg = SEM_UNDO;
    union semun arg;

    while (ret != 0) {
        if (semctl(sem_id, 1, GETVAL, arg) == -1) {
          perror("semctl GETVAL");
          return;
        }

        //printf("consumer1 Semaphore value is %d\n", arg.val);
    
        ret = semop(sem_id, &op, 1);
        if (ret == -1) {
            perror("semop");
            exit(1);
        }
        //printf("Consumer1 decremented semaphore value.\n");
        sleep(3);
    }
}


void producer(int sem_id) {
    int ret = -1;
    struct sembuf op;
    struct sembuf op1;
    op.sem_num = 0;
    op.sem_op = 1; // 增加信号量的值
    //op.sem_flg = SEM_UNDO; // 如果设置了SEM_UNDO标志,当线程结束时,系统会自动执行一个V操作(即sem_post),以恢复信号量的初始状态。producer先退出时候会导致consumer1异常,系统推荐设置
    union semun arg;

    while (ret != 0) {
        ret = semop(sem_id, &op, 1);
        if (ret == -1) {
            perror("semop");
            exit(1);
        }
        printf("Producer incremented semaphore 0 value.\n");
        if (semctl(sem_id, 0, GETVAL, arg) == -1) {
          perror("semctl GETVAL");
          return;
        }

        //printf("producer Semaphore value 0 is %d\n", arg.val);
        sleep(1);
    }

    ret = -1;
    op1.sem_num = 1;
    op1.sem_op = 1; // 增加信号量的值
    //op1.sem_flg = SEM_UNDO;

    while (ret != 0) {
        ret = semop(sem_id, &op1, 1);
        if (ret == -1) {
            perror("semop");
            exit(1);
        }
        printf("Producer incremented semaphore 1 value.\n");
        if (semctl(sem_id, 1, GETVAL, arg) == -1) {
          perror("semctl GETVAL");
          return;
        }

        //printf("producer Semaphore value 1 is %d\n", arg.val);
        sleep(1);
    }
}


总结

linux下 posix 和 systemV 信号量代码示例,可以运行

;