POSIX信号量的概念
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
信号量的本质是一个描述临界资源有效个数的计数器。
初始化、释放、等待、发布信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
基于环形队列的生产消费模型
之前的生产者和消费者的模型是基于队列queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序:
- 环形队列采用数组模拟,用模运算来模拟环状特性。
- 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。
- 但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。
代码实现
RingQueue.hpp:
#pragma once
#include<iostream>
#include<semaphore.h>
#include<vector>
#include<unistd.h>
#define NUM 10
class RingQueue{
private:
std::vector<int> v;
int max_cap;//最大个数
sem_t sem_blank;//生产者
sem_t sem_data;//消费者
int c_index;//消费者索引下标
int p_index;//生产者索引下标
void P(sem_t &s){//信号量等待
sem_wait(&s);
}
void V(sem_t &s){//信号量发布
sem_post(&s);
}
public:
RingQueue(int _cap=NUM):max_cap(_cap),v(_cap){
sem_init(&sem_blank,0,max_cap);
sem_init(&sem_data,0,0);
c_index=0;
p_index=0;
}
void Get(int& out){
P(sem_data);//读一个数据,消费者的信号量计数器减一
out=v[c_index];
c_index++;
c_index%=max_cap;//防止越界,构成环形队列
V(sem_blank);//读一个数据,生产者的信号量计数器加一,说明队列有一个空位
}
void Put(const int&in){
P(sem_blank);//入一个数据,生产者的信号量计数器减一,说明队列少了一个空位
v[p_index]=in;
p_index++;
p_index%=max_cap;//防止越界,构成环形队列
V(sem_data);//入一个数据,消费者的信号量计数器加一,说明队列有一个空位填了元素
}
~RingQueue(){
sem_destroy(&sem_data);
sem_destroy(&sem_blank);
c_index=0;
p_index=0;
}
};
main.cpp:
#include"RingQueue.hpp"
void*comsumer(void*ring_queue){
RingQueue*rq=(RingQueue*)ring_queue;
while(true){
sleep(1);//让生产者先生产
int data=0;
rq->Get(data);
std::cout<<"comsumer get data:"<<data<<std::endl;
}
}
void*producer(void*ring_queue){
RingQueue*rq=(RingQueue*)ring_queue;
int count=1;
while(true){
rq->Put(count);
count=count%5+1;
std::cout<<"producer put!"<<std::endl;
}
}
int main(){
pthread_t c,p;
RingQueue*rq=new RingQueue();
pthread_create(&c,nullptr,comsumer,rq);
pthread_create(&p,nullptr,producer,rq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
delete rq;
}
由于消费者一开始睡眠1秒,因此生产者会一下生产满,后续当消费者消费一个,生产者就会再生产一个,一直保持队列为满的状态。
使用信号量实现共享内存的同步通信
- server.cpp
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<unistd.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void P(int semid,unsigned short num)
{
struct sembuf set;
set.sem_num = num; //信号量在数组里的序号
set.sem_op = -1; //信号量的操作值
set.sem_flg = SEM_UNDO; //信号量的操作标识
semop(semid, &set,1);
}
void V(int semid,unsigned short num)
{
struct sembuf set;
set.sem_num = num;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(semid, &set,1);
}
int main()
{
key_t key1,key2;
key1 = ftok(".",50); //获取键值
key2 = ftok(".",10);
int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存
int semid = semget(key2,2,IPC_CREAT|0666);//打开或者创建信号量组
char *shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间
printf("shmat ok\n"); //表示连接成功
printf("%d\n",semid);
while(1)
{
P(semid,1); //等待第二个信号量释放
printf("%s\n",shmaddr);
V(semid,0); //释放第一个信号量
}
shmdt(shmaddr);
printf("quit\n");
return 0;
}
- client.cpp
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<unistd.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void P(int semid,unsigned short num)
{
struct sembuf set;
set.sem_num = num; //信号量在数组里的序号
set.sem_op = -1; //信号量的操作值
set.sem_flg = SEM_UNDO; //信号量的操作标识
semop(semid, &set,1);
}
void V(int semid,unsigned short num)
{
struct sembuf set;
set.sem_num = num;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(semid, &set,1);
}
int main()
{
key_t key1,key2;
key1 = ftok(".",50); //获取键值
key2 = ftok(".",10);
int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存
int semid = semget(key2,2,IPC_CREAT|0666);//打开或者创建信号量组
union semun seminit; //信号量初始化
seminit.val = 1; //第一个信号量设置为1
semctl(semid,0,SETVAL,seminit);
seminit.val = 0;//第二个信号量设置为0
semctl(semid,1,SETVAL,seminit);
char *shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间
printf("shmat ok\n");
printf("%d\n",semid);
char c='a';
for(;c<='z';c++)
{
sleep(1);
P(semid,0); //给第一个信号量上锁
//往共享内存中写入
shmaddr[c-'a']=c;
printf("%s\n",shmaddr);
V(semid,1); //释放第二个信号量
}
shmdt(shmaddr); //断开进程和内存的连接
shmctl(shmid,IPC_RMID,0); //删除共享内存段
semctl(semid,0,IPC_RMID); //删除信号量组
printf("quit\n");
return 0;
}