问题理解
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了共享内存的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。有一个很关键的问题就是要保证生产者和消费者不会同时访问共享内存。
解决方案
利用信号量和共享内存解决该问题,首先对PV等操作进行封装,设置两个信号量,一个初值为0,一个处置为1,利用对信号量的P操作和V操作可以保证生产者和消费者不会同时访问共享内存,并且在共享内存为空的时候,只有生产者能访问并把数据写入共享内存中,消费者不能访问。接着创建一个共享内存后利用生产者向共享内存写入数据(如字符等),后续中消费者在共享内存不为空时取出数据。
信号量
信号量机制主要用于实现进程间的同步,避免并发访问共享资源
函数
(一)获取或创建信号量
int semget(key_t key,int nsems,int semflg)
key:信号量标识
nsems:信号量数量,若新创建信号量:nsems>0,若打开已有的信号量:nsems可为0
semflg:存取权限或创建方式,为IPC_CREAT:若信号量不存在,则创建一个信号量,否则获取,为IPC_EXCL:只有信号量不存在的时候,新的信号量才建立,否则就产生错误。
返回值:成功返回信号量标识,出错返回-1
(二)获得或释放信号量
int semop(int semid,struct sembuf sops,unsigned nsops)*
semid:信号量标识
sops:指向由sembuf组成的数组
nsops:信号量数量
返回值:成功返回0,否则返回-1
(三)控制信号量
int semctl(int semid,int semnum,int cmd,union semun arg)
semid:信号量集的标识
semnum:操作信号在信号集中的编号,从0 开始
cmd:命令,用于指定操作类别,可以使用的命令有:
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID将信号量集从内存中删除。
GETALL用于读取信号量集中的所有信号量的值。
GETNCNT返回正在等待资源的进程数目。
GETPID返回最后一个执行semop操作的进程的PID。
GETVAL返回信号量集中的一个单个的信号量的值。
GETZCNT返回这在等待完全空闲的资源的进程数目。
SETALL设置信号量集中的所有的信号量的值。
(四)
struct sembuf{
unsigned short int sem_num;
short int sem_op;
}
sem_num:标识信号量集中的第几个信号量,从0 开始表示第一个,nsems-1表示最后一个
sem_op:标识对信号量所进行的操作类型,分为三种:
**sem_op>0**:对该信号量执行挂出操作,挂出值由sem_op决定,系统会把sem_op的值加到该信号量的当前值semval
**sem_op<0**:对该信号量执行等待操作,当信号量的当前值semval>=-sem_op时,semval减去sem_op的绝对值,为该线程分配对应数目的资源。相反,相应信号量的semncnt就加1,调用线程被阻塞,直到semval>=-sem_op,调用线程被唤醒,然后semncnt减去1
**sem_op=0**:表示调用者希望semval变为0,若为0则立即返回,若不为0,相应信号量semzcnt就加1,调用线程被阻塞。
semncnt:等待semval变为大于当前值的线程数
semzcnt:等待semval变为0的线程数
共享内存
共享内存是两个正在运行的进程之间传递数据的一种非常有效的方式。共享内存的具体实现是不同进程共享的内存安排为同一个物理地址
函数
(一)获得或创建共享内存
int shmget(key_t key,int size,int shmflg)
size:共享内存的大小
shmflg:存取权限或创建条件,若为IPC_CREAT|perm perm为存取权限,则表示创建共享内存,为0表示获得共享内存
返回值:执行成功返回内存标志符,失败返回-1
(二)将共享内存连接到进程
void * shmat(int shmid,const void shmaddr,int shmflg)
shmid:共享内存标识
shmaddr:指定共享内存连接到当前进程的地址位置,一般为NULL,表示由系统选择对应的地址shmflg:指定如何使用共享内存,若指定了SHM_RDONLY位则表示以只读*的方式使用此段,否则以读写的方式(SHM_RND)使用此段
返回值:执行成功返回共享内存的首地址,失败返回-1
(三)分离共享内存
int shmdt(const void shmaddr)*
shmaddr:共享内存的地址指针
返回值:执行成功返回0,否则返回-1
(四)控制函数
*int shmctl(int shmid,int cmd,struct shmid_ds buf)
shmid:共享内存标识符
cmd:
IPC_STAT 获取共享内存的状态
IPC_/SET设置共享内存的权限
IPC_RMID删除共享内存
IPC_LOCK 锁定共享内存,使共享内存不被置换出去
IPC_UNLOCK解锁
struct shmid_ds{
struct ipc_perm shm_perm; //存取权限
int shm_segsz; //共享内存大小
__kernel_time_t shm_atime; //最后映射时间
__kernel_time_t shm_dtime; //最后解除映射时间
__kernel_time_t shm_ctime; //最后修改时间
__kernel_ipc_pid_t shm_cpid; //创建进程ID
__kernel_ipc_pid_t shm_lpid; //最近操作进程ID
unsigned short shm_nattch; //建立映射的进程数
}
实现生产者消费者问题
vim allsemshm.c
创建了2个信号量用于实现生产者和消费者之间的同步问题 ,并创建了一个共享内存作为共享资源
vim semshm_s.c
生产者
vim semshm_c.c
消费者
vim delsemshm.c
删除所建的信号量和共享内存
结果
先对四个文件进行编译
运行allsemshm,建立信号量和共享内存
运行semshm_s生产者程序(会sleep15秒)
同时,运行2个semshm_c消费者程序,观察程序的输出(在这里的时候一定要快点打开两个终端,因为sleep的时间不算长,速度慢了看的效果会不太明显)
再调用delsemshm程序删除信号量和共享内存
感想
在最开始的时候花了大量的时间去看跟信号量和共享内存有关的一些函数,一些函数参数的取值有多种情况,代表不同的含义,最后大概懂了这些函数的作用。但是在生产者和消费者之实例中却不会用这些函数,然后看了很久老师所给链接中的代码,稍微看懂了代码部分,但是在我写生产者和消费者实现的时候因为对信号量和共享内存的相关函数不会使用,模仿写完了该问题的实现,但是出现了很多问题,暂时还不能编译。。。这个实现真的好难呐。。。