linux信号量PV操作

/**
 * @file 03_semaphore.c
 * @author zfc
 * @brief 使用信号量 完成消费和生产工作:
 *      生产者生产产品,消费者消费产品。
 *      当生产者 生产产品时,如果没有缓冲区(仓库)可用,那么生产过程必须等待消费者进程释放出一个缓冲区;
 *      当消费者 进程消费产品时,如果 仓库 产品为空,那么消费者进程将被阻塞,指导新的产品被生产出来。
 *      模拟 一个生产者 两个消费者 的情况。
 *      参考:多个使用信号量的例子https://wenku.baidu.com/view/8954def0c2c708a1284ac850ad02de80d4d80608.html
 *  
 *   *  需要注意:无论在生产者进程还是在消费者进程,两个P操作的次序不能颠倒,
 *      应先执行同步信号的P操作,然后再执行互斥信号的P操作,否则可能造成死锁。
 * 
 *   *  PV操作的意义:我们用信号量及PV操作来实现进程的同步和互斥。PV操作属于进程的低级通信。
 *      临界资源(互斥资源):一次只允许一个进程使用的资源就是临界资源
 *      信号量其实就是由一个计数器和一个等待队列组成
 * 
 *   *  执行一次P操作 意味着请求分配一个单位资源
 *      执行一次V操作 意味着释放一个单位资源
 * 
 *   *  此次实验,再次明白 线程间的资源共享,而进程间资源不共享,是各自复制一份。
 *      而PV操作是实现 操作共享资源的同步/互斥。
 * 
 * @version 0.1
 * @date 2022-08-31
 * 
 * @copyright Copyright (c) 2022
 */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <pthread.h>

#define SEM_KEY 90

//打印错误
#define PERROR(errStr)  { perror(errStr); exit(-1); }

//打印数组
#define PRINT_ARR(arr, count)           \
    {                                   \
        printf(#arr" : ");              \
        for(int i=0; i<count; i++)      \
        {                               \
            printf("%d  ", arr[i].val);     \
        }                               \
        puts("\n");                                \
     }

key_t key;              // semphore 键值
static int product=0;   // 产品数量
    
pthread_t t_consumer1;  // 两个消费者线程
pthread_t t_consumer2;

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};
union semun var[3];
int semId;  //信号量标识符

static void semphore_P(int semid, int val);
static void semphore_V(int semid, int val);

void *Consumer1(void);
void *Consumer2(void);

int main()
{
    //1. 标准化 key值
    key = ftok(".", SEM_KEY);
    if(key == -1)   PERROR("ftok error");

    //2. 创建 一个信号量集 <—— key
    semId = semget(key, 3, IPC_CREAT | 0660); //IPC_CREAT = 01000
    //  几次实验都是 semId=0
    if(semId == -1) PERROR("semget error");

    printf("[LOG_ZFC]: semId = %d\n", semId);

    //3. 创建3个信号量 并 初始化每个信号量的val
    var[0].val = 1;     // mutex 用于几个生产者之间 互斥 
                        //      P操作-1 ——> var[0].val=0    =0说明没有可用资源——>其他进程(消费者)被阻塞(进入等待队列),等待该资源
                        //                                 <0 的绝对值表示等待该资源的 进程数
                        //                                 >0 时该进程继续执行,否则释放队列中第一个等待信号的进程
                        //      V操作+1 ——> var[0].val=1    
    var[1].val = 5;     // "空仓"数量=5   
    var[2].val = 0;     // "满仓"数量=0

    //4. 将信号量 加入 semId信号量集
    for(int j=0; j<3; j++)
    {
        int ret = semctl(semId, j, SETVAL, var[j]);
        if(ret == -1)   PERROR("semctl error\n");
    }

    int ret;
    //5. 创建线程 角色分配
        //5-1. 消费者 1
    ret = pthread_create(&t_consumer1, NULL, (void *)Consumer1, NULL);
    if(ret != 0)   PERROR("pthread1 create error\n");
        //5-2. 消费者 2
    ret = pthread_create(&t_consumer2, NULL, (void *)Consumer2, NULL);
    if(ret != 0)   PERROR("pthread2 create error\n");

        //5-3. 生产者 (主程序)
    while(1)
    {
        semphore_P(semId, 1);   // 空仓数量 -1
        semphore_P(semId, 0); //mutex 上锁

        product++;
        printf("0productor+1, product: %d\n\n", product);
        //PRINT_ARR(var, 3);

        semphore_V(semId, 0); //mutex 开锁
        semphore_V(semId, 2);   // 满仓数量 +1
        //sleep(1);
    }

    pthread_join(t_consumer1, NULL);
    pthread_join(t_consumer2, NULL);

    return 0;
}

//消费者 1
void *Consumer1(void)
{
    while(1)
    {
        sleep(2);
        semphore_P(semId, 2);   // 满仓数量 -1
        semphore_P(semId, 0);   //mutex 上锁

        product-- ;
        printf("2consumer-1, product: %d\n\n", product);
        //PRINT_ARR(var, 3);

        semphore_V(semId, 0);    //mutex 开锁
        semphore_V(semId, 1);   // 空仓数量 +1
    }
}

//消费者 2
void *Consumer2(void)
{
    while(1)
    {
        sleep(1);
        semphore_P(semId, 2);   // 满仓数量 -1
        semphore_P(semId, 0); //mutex 上锁

        product-- ;
        printf("1consumer-1, product: %d\n\n", product);
        //PRINT_ARR(var, 3);

        semphore_V(semId, 0); //mutex 开锁
        semphore_V(semId, 1);   // 空仓数量 +1
    }

}

/** (通用)
 * @brief 信号量 P操作      P:通过
 * 
 * @param semid 信号量集 的 id
 * @param val 信号量 编号
 */
void semphore_P(int semid, int val)
{
    //1. 确定 操作信号量:哪个信号集中的,哪一个,执行什么操作
    struct sembuf sembuf_p;
    sembuf_p.sem_num = val;         // 操作哪个信号量
    sembuf_p.sem_op = -1;           // p 操作
    sembuf_p.sem_flg = SEM_UNDO;    // 进程形式

    //2. 执行 操作
    int ret = semop(semid, &sembuf_p, 1);     
    if(ret == -1)   PERROR("semop error");

    //PRINT_ARR(var, 3);

}

/** (通用)
 * @brief 信号量 V操作      V:释放
 * 
 * @param semid 信号量集 的 id
 * @param val 信号量 编号
 */
void semphore_V(int semid, int val)
{
    //1. 确定 操作信号量:哪个信号集中的,哪一个,执行什么操作
    struct sembuf sembuf_v;
    sembuf_v.sem_num = val;         // 操作哪个信号量
    sembuf_v.sem_op = +1;           // V 操作
    sembuf_v.sem_flg = SEM_UNDO;    // 进程形式

    //2. 执行 操作
    int ret = semop(semid, &sembuf_v, 1);     
    if(ret == -1)   PERROR("semop error");

    // PRINT_ARR(var, 3);
}

 

posted @ 2022-09-01 20:39  周二zfc  阅读(81)  评论(0)    收藏  举报