信号量
本质:计数器 + 等待队列 + 向外提供的使执行流阻塞 / 唤醒的功能接口
实现同步的原理:进程获取临界资源之前,要先获取信号量资源;
实现互斥的原理:一个进程获取了该临界资源之后,另一个进程无法再访问该临界资源。(0/1计数器)
对资源进行计数,统计当前的资源数量,通过自身的计数,就可以进行条件判断,是否能够进行操作,若不能获取资源,则阻塞当前执行流。
在程序初始化阶段,根据实际资源数量初始化信号量计数器数值,在每次获取资源之前,先获取信号量(先去判断计数是否大于0,若大于0,则计数-1,直接返回,获取数据;否则阻塞当前执行流)
其它执行流生产一个资源后,先判断计数器是否 <0 ,若小于0,则唤醒一个执行流,然后进行计数 +1
接口介绍:
sem_t sem;
int sem_init(sem_t *sem, int pshared, int value); //初始化操作
//pshared:这个参数决定了当前的信号量用于进程间还是线程间:0-线程间 !0-进程间
//value:实际的资源数量,用于初始化信号量计数器初值
int sem_wait(sem_t *sem); //阻塞操作---若没有资源则直接阻塞
int sem_post(sem_t *sem); //唤醒操作
int sem_destroy(sem_t *sem); //销毁操作
代码实现:
#include <cstdio>
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#define QUEUE_MAX 5
class RingQueue{
public:
RingQueue(int maxq = QUEUE_MAX):_queue(maxq), _capacity(maxq), _step_read(0), _step_write(0){
//sem_init(信号量, 进程/线程(0)标志, 信号量初值)
sem_init(&_lock, 0, 1); //用于实现互斥锁
sem_init(&_sem_data, 0, 0); //数据空间计数初始为0
sem_init(&_sem_idle, 0, maxq); //空闲空间计数初始为数组容量
}
~RingQueue(){
sem_destroy(&_lock);
sem_destroy(&_sem_data);
sem_destroy(&_sem_idle);
}
bool push(int data){
//1.判断是否能够访问资源,不能访问则阻塞
sem_wait(&_sem_idle);//空闲空间计数的判断,空闲空间计数 -1
//2.能访问,则加锁,保护访问过程
sem_wait(&_lock);//lock计数不大于1,当前若可以访问则-1,别人就不能访问了
//3.资源的访问
_queue[_step_write] = data;
_step_write = (_step_write + 1) % _capacity;//走到最后,从头开始
//4.解锁
sem_post(&_lock);//lock计数+1,唤醒其它因为加锁阻塞的线程
//5.入队数据之后,数据空间计数+1,唤醒消费者
sem_post(&_sem_data);
return true;
}
bool pop(int *data){
sem_wait(&_sem_data);//有没有数据
sem_wait(&_lock);//有数据则加锁保护访问数据的过程
*data = _queue[_step_read]; //获取数据
_step_read = (_step_read + 1) % _capacity;
sem_post(&_lock);//解锁操作
sem_post(&_sem_idle);//取出数据,则空闲空间计数+1,唤醒生产者
return true;
}
private:
std::vector<int> _queue; //数组 vector需要初始化节点数量
int _capacity; //队列的容量
int _step_read; //获取数据的位置下标
int _step_write; //写入数据的位置下标
sem_t _lock; //这个信号量用于实现互斥
//这个信号量用于对空闲时间进行计数
//---对于生产者来说空闲空间计数 >0 的时候才能写数据 --- 初始为节点个数
sem_t _sem_idle;
//这个信号用于对具有数据的空间进行计数
//---对于消费者来说有数据的空间计数 >0 的时候才能取出数据 --- 初始为0
sem_t _sem_data;
};
void *thr_productor(void *arg){
//这个参数是我们的主线程传递过来的线程
RingQueue *queue = (RingQueue*)arg; //类型强转
int i = 0;
while(1){
//生产者不断生产数据
queue->push(i);//通过Push接口操作queue中的成员变量
printf("productor push data:%d\n", i++);
}
return NULL;
}
void *thr_customer(void *arg){
RingQueue *queue = (RingQueue*)arg;
while(1){
//消费者不断获取数据进行处理
int data;
queue->pop(&data);
printf("customer pop data:%d\n",data);
}
return NULL;
}
int main(){
pthread_t ptid[4], ctid[4];
int ret, i;
RingQueue queue;
for(i = 0; i < 4; i++){
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
if(ret != 0){
printf("creat productor thread error\n");
return -1;
}
ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
if(ret != 0){
printf("creat customer thread error\n");
return -1;
}
}
for(i = 0; i < 4; i++){
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}
运行结果: