semaphore 使用介绍

本文详细介绍了Linux中的信号量机制,包括无名信号量、有名信号量和SYSTEMV信号量的基本函数及使用。通过示例展示了信号量在多线程和多进程中的应用,如互斥、同步、生产者消费者模型等。此外,还阐述了信号量在进程间同步和互斥中的关键作用。
摘要由CSDN通过智能技术生成


一、semaphore 简介

信号量广泛用于进程或线程间的同步和互斥,本质上是一个非负整数计数器,当信号量值大于 0 时,则可以访问公共资源,否则将阻塞访问
PV 是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1
linux 信号量分两种:

  • 内核信号量
  • 用户态信号量,又分两种:
    • POSIX信号量,又分为两种:
      • 无名信号量,一般用于线程间和父子进程间同步或互斥,保存在内存中
      • 有名信号量,一般用于普通进程间同步或互斥,创建一个文件保存在文件中
    • SYSTEM V信号量,是一个或多个信号量的集合,具体操作和 POSIX 信号量大同小异
      在这里插入图片描述

二、无名信号量基本函数

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

pshared:控制信号量的类型,0表示线程间共享,其它表示进程间共享
value:信号量的初始值

2. int sem_post(sem_t *sem);

是以原子操作的方式给信号量的值加1(V操作),并发出信号唤醒等待线程 sem_wait

3. int sem_wait(sem_t *sem);

以原子操作的方式给信号量的值减1(P操作),如果信号量的值为0函数将会等待,直到有线程增加了该信号量的值使其不再为0

4. int sem_trywait(sem_t *sem);

非阻塞方式,如果信号量的值为0立即返回,返回错误 EAGAIN

5. int sem_getvalue(sem_t *sem, int *sval);

获取信号量的值,保存在 sval 中

6. int sem_destory(sem_t *sem);

用于销毁信号量,释放所有相关联的资源(由 sem_init 自动申请的资源)

三、有名信号量基本函数

1. sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

name: 不能写路径,默认创建的文件放在 “/dev/shm/sem.xxx”
oflag: O_CREAT: 若信号量不存在则创建,且mode和value必须有效。若信号量已存在则直接打开,且忽略mode和value参数。O_CREAT|O_EXCL: 若信号量已存在,该函数会直接返回error
value: 信号量的初始值

2. int sem_close(sem_t *sem);

关闭有名信号量

3. int sem_unlink(const char *name);

删除有名信号量文件和回收分配的资源,调用 sem_unlink 回收有名信号量资源时,要确定所有对这个有名信号量的引用都已经通过 sem_close 关闭了,如果有任何的进程或是线程还在引用这个信号量,sem_unlink 不会起到任何的作用,即必须是最后一个使用该信号量的进程来执行才有效,因为每个信号量都有一个引用计数器记录当前的打开次数,sem_unlink 必须等待计数为0时才把信号量从文件系统中删除

四、SYSTEM V 信号量基本函数

1. struct semid_ds

信号量集合数据结构,此数据结构中定义了整个信号量集的基本属性

struct semid_ds
{
    struct ipc_perm    sem_perm;         /* permissions .. see ipc.h */
    __kernel_time_t    sem_otime;        /* last semop time */
    __kernel_time_t    sem_ctime;        /* last change time */
    struct sem    *sem_base;             /* ptr to first semaphore in array */
    struct sem_queue *sem_pending;       /* pending operations to be processed */
    struct sem_queue **sem_pending_last; /* last pending operation */
    struct sem_undo    *undo;            /* undo requests on this array */
    unsigned short    sem_nsems;         /* no. of semaphores in array */
};

2. struct sem

信号量数据结构,此数据结构中定义了单个信号量的基本属性

struct sem
{
    int    semval;        /* current value */
    int    sempid;        /* pid of last operation */
    struct list_head sem_pending; /* pending single-sop operations */
};

3. int semget(key_t key, int nsems, int semflg);

创建或打开一个信号量集合,该集合中可以包含多个信号量
key:进程间通信键值,通过调用 ftok() 函数得到
nsems:创建的信号量的个数。如果只是访问可以指定为 0,一旦创建了该信号量集合就不能更改其信号量个数
semflg:标识函数的行为及信号量的权限,IPC_CREAT:创建信号量;IPC_EXCL:检测信号量是否存在

4. int semctl(int semid, int semnum, int cmd, xxx);

对信号量集合以及集合中的信号量进行控制
semnum:集合中信号量的序号,指定对哪个信号量操作, 只对某些 cmd 操作有意义
cmd:信号量控制类型,semctl 参数由 cmd 决定,可能有3个或4个。当4个参数时第4个为自定义联合体,用于设置或获取信息

union semun{
    int            val;        /*信号量值*/
    struct semid_ds *buf;      /*信号量集合信息*/
    unsigned short  *array;    /*信号量值数组*/
    struct seminfo  *__buf;    /*信号量信息数组*/
};

5. int semop(int semid, struct sembuf *sops, unsigned nsops);

操作信号量,主要进行信号量加减操作
sem_op > 0:信号量的值在原来的基础上加上此值
sem_op < 0:如果信号量的值大于等于sem_op绝对值,则信号量的值减去绝对值,如果信号量的值小于绝对值,则挂起操作进程
sem_op = 0:对信号量的值进行是否为 0 测试。若为 0 则函数立即返回,若不为 0 则阻塞调用进程
sem_flag = IPC_NOWAIT:在对信号量的操作不能执行的情况下使函数立即返回
sem_flag = SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销

struct sembuf{
    unsigned short  sem_num; 	/*信号量序号*/
    short       sem_op;         /*信号量操作值*/
    short       sem_flg;     	/*信号量操作标识*/
};

五、无名信号量多线程应用

1. 无名信号量多线程互斥

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int number = 1;
sem_t sem;

void* thread_fun1(void *arg)
{
    sem_wait(&sem);
    number++;
    printf("thread_fun1: number = %d\n", number);
    sem_post(&sem);
}

void* thread_fun2(void *arg)
{
    sem_wait(&sem);
    number--;
    printf("thread_fun2: number = %d\n", number);
    sem_post(&sem);
}

int main(int argc, char *argv[])
{
    pthread_t id1, id2;

    sem_init(&sem, 0, 1);
    pthread_create(&id1, NULL, thread_fun1, NULL);
    pthread_create(&id2, NULL, thread_fun2, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    sem_destroy(&sem);
    return 0;
}

上面的例程,哪个线程先申请到信号量资源是随机的:
在这里插入图片描述

2. 无名信号量多线程同步

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int number = 1;
sem_t sem1, sem2;

void* thread_fun1(void *arg)
{
    sem_wait(&sem1);
    number++;
    printf("thread_fun1: number = %d\n", number);
    sem_post(&sem2);
}

void* thread_fun2(void *arg)
{
    sem_wait(&sem2);
    number--;
    printf("thread_fun2: number = %d\n", number);
    sem_post(&sem1);
}

int main(int argc,char *argv[])
{
    pthread_t id1, id2;

    sem_init(&sem1, 0, 0);
    sem_init(&sem2, 0, 1);
    pthread_create(&id1, NULL, thread_fun1, NULL);
    pthread_create(&id2, NULL, thread_fun2, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    return 0;
}

如果想按特定顺序执行,可以使用2个信号量实现同步。上面例程约束线程2先执行完,然后线程1才继续执行:
在这里插入图片描述

3. 无名信号量多线程同步 - 生产者消费者

有一个长度为N的缓冲池为生产者和消费者所共有
只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息
生产者往缓冲池放信息的时候,消费者不可操作缓冲池,反之亦然

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

#define BUFF_SIZE 5
char count = 0;
pthread_mutex_t mutex;
sem_t semCanCustom;
sem_t semCanProduct;

void* thread_producer(void* arg)
{
    while (1)
    {
        sem_wait(&semCanProduct);
        pthread_mutex_lock(&mutex);
        count++;
        printf("count %d producer\n", count);
        pthread_mutex_unlock(&mutex);
        sem_post(&semCanCustom);
        sleep(0.5);
    }
}

void* thread_consumer(void* arg)
{
    while (1)
    {
        sem_wait(&semCanCustom);
        pthread_mutex_lock(&mutex);
        count--;
        printf("count %d consumer\n", count);
        pthread_mutex_unlock(&mutex);
        sem_post(&semCanProduct);
        sleep(0.5);
    }
}

int main()
{
    pthread_t ptid, ctid;

    pthread_mutex_init(&mutex, NULL);
    sem_init(&semCanCustom, 0, 0);
    sem_init(&semCanProduct, 0, BUFF_SIZE);
    pthread_create(&ptid, NULL, thread_producer, NULL);
    pthread_create(&ctid, NULL, thread_consumer, NULL);
    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);
    pthread_mutex_destroy(&mutex);
    sem_destroy(&semCanCustom);
    sem_destroy(&semCanProduct);
    return 0;
}

在这里插入图片描述

4. 无名信号量父子进程互斥

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char **argv)
{
    int     fd, count=0, loop=5;
    void*   ptr;
    pid_t   pid;
    sem_t   sem;

    fd = open("log.txt", O_RDWR|O_CREAT, S_IRWXU);
    write(fd, &count, sizeof(int));
    ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    sem_init(&sem, 1, 1);
    pid = fork();
    if (0 == pid)
    {
        for (int i = 0; i < loop; i++)
        {
            sem_wait(&sem);
            printf("child: %d\n", (*(int*)ptr)++);
            sleep(rand()%3);
            sem_post(&sem);
        }
    }
    else
    {
        for (int i = 0; i < loop; i++)
        {
            sem_wait(&sem);
            printf("parent: %d\n", (*(int*)ptr)++);
            sleep(rand()%3);
            sem_post(&sem);
        }
    }
    sem_destroy(&sem);
    return 0;
}

在这里插入图片描述

5. 无名信号量父子进程同步

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char **argv)
{
    int     fd, count=0, loop=5;
    void*   ptr;
    pid_t   pid;
    sem_t   sem1, sem2;

    fd = open("log.txt", O_RDWR|O_CREAT, S_IRWXU);
    write(fd, &count, sizeof(int));
    ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    sem_init(&sem1, 1, 1);
    sem_init(&sem2, 1, 0);
    pid = fork();
    if (0 == pid)
    {
        for (int i = 0; i < loop; i++)
        {
            sem_wait(&sem1);
            printf("child: %d\n", (*(int*)ptr)++);
            sleep(rand()%3);
            sem_post(&sem2);
        }
    }
    else
    {
        for (int i = 0; i < loop; i++)
        {
            sem_wait(&sem2);
            printf("parent: %d\n", (*(int*)ptr)++);
            sleep(rand()%3);
            sem_post(&sem1);
        }
    }
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    return 0;
}

运行结果如下,无打印输出,待调试。。。
在这里插入图片描述

六、有名信号量多进程应用

1. 有名信号量进程间互斥

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

void printer(sem_t *sem, const char *str)
{
    sem_wait(sem);
    while (*str!='\0')
    {
        putchar(*str);
        fflush(stdout);
        str++;
        sleep(1);
    }
    printf("\n");
    sem_post(sem);
}

int main(int argc, char *argv[])
{
    pid_t pid;
    sem_t *sem = NULL;

    pid = fork();
    if (0 == pid)
    {
        // 不同进程只要名字一样,打开的就是同一个有名信号量
        sem = sem_open("wenhchenSem", O_CREAT|O_RDWR, 0666, 1);
        const char *str1 = "hello";
        printer(sem, str1);
        sem_close(sem);
        _exit(1);
    }
    else
    {
        sem = sem_open("wenhchenSem", O_CREAT|O_RDWR, 0666, 1);
        const char *str2 = "world";
        printer(sem, str2);
        sem_close(sem);
        wait(NULL);
    }
    sem_unlink("wenhchenSem");
    return 0;
}

在这里插入图片描述

2. 有名信号量进程间同步

// print1.cpp
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>

void print(sem_t *print1, sem_t *print2)
{
    int i = 0;
    while(1)
    {
        sem_wait(print1);
        i++;
        printf("print1 i = %d\n", i);
        sem_post(print2);
    }
}

int main(int argc, char **argv)
{
    sem_t *print1, *print2;

    print1 = sem_open("printSem1", O_CREAT, 0666, 0);
    print2 = sem_open("printSem2", O_CREAT, 0666, 1);
    print(print1, print2);
    return 0;
}


// print2.cpp
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>

void print(sem_t *print1, sem_t *print2)
{
    int i = 0;
    while(1)
    {
        sem_wait(print2);
        i++;
        printf("print2 i = %d\n", i);
        sleep(1);
        sem_post(print1);
    }
}

int main(int argc, char **argv)
{
    sem_t *print1, *print2;

    print1 = sem_open("printSem1", O_CREAT, 0666, 0);
    print2 = sem_open("printSem2", O_CREAT, 0666, 1);
    print(print1, print2);
    return 0;
}

当 print1 先启动时,没有打印,只有当 print2 启动后才开始有打印:
在这里插入图片描述

七、SYSTEM V进程间互斥

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

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

int P(int semid)
{
    struct sembuf sb;

    sb.sem_num = 0;
    sb.sem_op = -1;
    sb.sem_flg = SEM_UNDO;
    semop(semid, &sb, 1);
    return 0;
}

int V(int semid)
{
    struct sembuf sb;

    sb.sem_num = 0;
    sb.sem_op = 1;
    sb.sem_flg = SEM_UNDO;
    semop(semid, &sb, 1);
    return 0;
}

int main(int argc, char **argv)
{
    pid_t pid;
    int i, shmid, semid;
    int *ptr = NULL;
    union semun semopts;

    shmid = shmget(0x33, sizeof(int), IPC_CREAT|0600); // 创建一块共享内存, 存一个 int 变量
    ptr = (int*)shmat(shmid, NULL, 0); // 将共享内存映射到进程
    *ptr = 0; // 赋值为 0
    semid = semget(0x44, 1, IPC_CREAT|0600); // 创建一个信号量用来同步共享内存的操作
    semopts.val = 1; // 初始化信号量值为 1
    semctl(semid, 0, SETVAL, semopts);

    pid = fork();
    if (0 == pid)
    {
        for (i = 0; i < 100000; i++)
        {
            P(semid);
            (*ptr)++;
            V(semid);
            printf("child: %d\n", *ptr);
        }
    }
    else
    {
        for (i = 0; i < 100000; i++)
        {
            P(semid);
            (*ptr)--;
            V(semid);
            printf("parent: %d\n", *ptr);
        }
        wait(NULL);
        printf("finally: %d\n", *ptr); // 如果同步成功, 共享内存的值为 0
    }
    semctl(semid, 0, IPC_RMID); // IPC_RMID 删除信号量集合
    return 0;
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值