概述
作用:实现进程或线程间的同步与互斥
本质:就是一个内核中的计数器+pcb等待队列
原理:对资源进行计数,在进程获取资源之前先通过计数判断获取是否合理;不合理则阻塞等待,直到条件满足后唤醒阻塞进程
同步的实现:获取资源前进行P操作,合理则获取,不合理则阻塞;产生一次资源进行一次V操作
互斥的实现:计数最大为1,表示只有一个资源,访问前执行P操作,在访问期间其他进程不可访问,访问完成后执行V操作;
P操作:计数-1,判断访问是否合理,不合理则阻塞;合理则正确返回
V操作:计数+1,唤醒阻塞进程
操作接口
头文件:#include <semaphore.h>
- 定义信号量:
sem_t enm;
- 初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem: 信号量变量;
pshared: 区分是用于线程间同步互斥还是进程间同步互斥: 值为0–线程间; 值为!0–进程间
value: 初值
返回值:成功,返回0;失败,返回-1; - P操作:
int sem_wait(sem_t *sem);
–阻塞
int sem_trywait(sem_t *sem);
–非阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
–设定阻塞时间进行P操作,超时则报错返回
4.V操作:int sem_post(sem_t *sem);
5.销毁信号量:sem_destroy(sem_t *sem);
信号量与条件变量的区别与关系
信号量和条件变量都用于实现互斥与同步;
条件变量的使用中,我们需要自己判断条件是否满足,而信号量不需要
所以,条件变量的使用需要搭配互斥锁,信号量不需要.
使用信号量实现消费者生产者模型
环形队列的实现
通常是同通过数组来实现,定义两个索引,一个读取位置read一个写入位置write;
要写入数据时,write向后走,取模数组长度可以防止越界。
读数据时,read向后走,读到write所在位置表示数据全读完了。
write与read相遇就表示数组存满,无法继续存入数据。
class RingQueue{
private:
std::vector<int> _arry; //封装一个vector
int _read; //读索引
int _write; //写索引
int _capacity; //容量
sem_t sem_lock; //用于实现互斥的信号量
sem_t sem_idle; //记录空闲节点的信号量
sem_t sem_data; //记录数据节点的信号量
public:
};
生产者消费者模型完整代码
注意:此处的Push和Pop中,加锁解锁操作必须放在资源访问之后
,因为如果不满足资源访问条件就会发生阻塞,若是在其之前加锁就会因为阻塞而导致该锁无法解开,程序会发生错误。
不同于条件变量
一开始就要进行加锁是因为使用条件变量需要程序员自行判断资源访问是否合理
,而这里判断资源访问是否合理的操作其实也是在访问临界资源,所以需要先加锁;信号量不需要程序员判断资源访问是否合理
,所以不需要一开始就加锁。
#include<vector>
#include<iostream>
#include<semaphore.h>
#include<cstdio>
#include<pthread.h>
class RingQueue{
private:
int _capacity; //容量
std::vector<int> _arry; //封装一个vector
int _read; //读索引
int _write; //写索引
sem_t sem_lock; //用于实现互斥的信号量
sem_t sem_idle; //记录空闲节点的信号量
sem_t sem_data; //记录数据节点的信号量
public:
RingQueue(int cap=5):_capacity(cap),_arry(cap),_read(0),_write(0)
{
sem_init(&sem_lock,0,1);
sem_init(&sem_idle,0,cap);
sem_init(&sem_data,0,0);
}
bool Push(int data){
sem_wait(&sem_idle); //判断是否有空结点可供插入,没有则阻塞
sem_wait(&sem_lock); //加锁
_arry[_write]=data;
_write=(_write+1)%_capacity;
sem_post(&sem_lock); //解锁
sem_post(&sem_data); //数据结点+1,唤醒消费者
return true;
}
bool Pop(int* data){
sem_wait(&sem_data); //判断是否有数据节点可供处理,没有则阻塞
sem_wait(&sem_lock); //加锁
*data=_arry[_read];
_read=(_read+1)%_capacity;
sem_post(&sem_lock); //解锁
sem_post(&sem_idle); //空结点+1,唤醒生产者
return true;
}
~RingQueue(){
sem_destroy(&sem_lock);
sem_destroy(&sem_idle);
sem_destroy(&sem_data);
}
};
void* productor(void* arg){
RingQueue* q=(RingQueue*)arg;
int i=0;
while(1){
q->Push(i);
printf("生产者入队数据:%d\n",i++);
}
return NULL;
}
void* customer(void* arg){
RingQueue* q=(RingQueue*)arg;
while(1){
int data;
q->Pop(&data);
printf("消费者出队数据:%d\n",data);
}
return NULL;
}
int main(){
RingQueue q;
pthread_t ptid[4],ctid[4];
int ret;
for(int i=0;i<4;i++){
ret=pthread_create(&ptid[i],NULL,productor,&q);
if(ret!=0){
printf("create thread error\n");
return -1;
}
}
for(int i=0;i<4;i++){
ret=pthread_create(&ctid[i],NULL,customer,&q);
if(ret!=0){
printf("create thread error\n");
return -1;
}
}
for(int i=0;i<4;i++){
pthread_join(ptid[i],NULL);
pthread_join(ctid[i],NULL);
}
return 0;
}
部分结果: