/**
* @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);
}