什么是信号量?
信号量是用来用于同步和互斥的。其实就是一个计数器,进行PV操作,其中P操作就是让计数器–,V操作就是让计数器++。
从物理上说明信号量的P、V操作的含义。 P(S)表示申请一个资源,S.value>0表示有资源可用,其值为资源的数目;S.value=0表示无资源可用;S.value<0, 则|S.value|表示S等待队列中的进程个数。V(S)表示释放一个资源,信号量的初值应该大于等于0。P操作相当于“等待一个信号”,而V操作相当于“发送一个信号”,在实现同步过程中,V操作相当于发送一个信号说合作者已经完成了某项任务,在实现互斥过程中,V操作相当于发送一个信号说临界资源可用了。实际上,在实现互斥时,P、V操作相当于申请资源和释放资源。
信号量通常用于生产者消费者模型
进程互斥
- 由于各进程的要求共享资源,而且有些资源互斥使用,因此各进程之间竞争使用这些资源,进程的这种关系就叫做互斥
- 系统中某些资源一次只允许一个进程使用,称这样的进程资源为临界资源或互斥资源
- 在进程中涉及到的互斥资源叫做临界区。
进程同步
进程同步是指在多个并发执行的进程之间协调和控制它们的执行顺序,以及管理它们对共享资源的访问,以避免竞态条件(Race Condition)和数据不一致性等并发问题。在多任务操作系统中,进程同步是一个关键的概念,确保进程能够正确地协同工作而不会产生意外的结果。
现在有这样一个场景,看电影
看电影一定要有位置(资源),只有当我们买了票,我们就拥有了这个位置(此时相当于P操作),资源会减少一个,当我们观看完电影,我们就会离开。这时位置就不属于你了(此时就是V操作)。
所以我们就有了以下的信息:
1.申请信号量的本质:让信号量计数器–
2.主要申请信号量成功,临界资源内部,一定会给你预留了你想要的资源——申请信号量的本质其实就是对临界区资源的预定机制
相关接口
信号量的初始化
sem:自己定义的信号量变量
pshared:0表示线程间的共享,非0表示进程间共享
value:信号量初始值(资源数)
信号量的销毁
信号量的等待
等待信号量,其实就是P操作,将计数器–
信号狼的发布
其实就是信号量的V操作,将计数器++
基于环形队列的生产者消费者模型
环形队列其实本质上不是一个真正的环形, 它其实本质是一个数组。
之所以是环形,是我们将它抽象了出来,更好的理解。
环形队列里面的内容就是我们要访问的临界资源,我们的生产者和消费者可能会同时访问同一个资源。
这时有两种情况:
1.资源为空,这种情况没有资源了,消费者就无法访问,就应该阻塞
2.资源为满,生产者不应该生产,阻塞
其他情况就是生产者和消费者在不同的临界资源生产和消费。
因为生产者和消费者是并发指向的,所以我们就需要保证同步和互斥。
所以,我在这里设置了2个信号量,一个代表了空间资源,一个代表了数据资源。当空间资源为满的时候,生产者就不应该生产,反之数据资源也一样。
//将sem封装了起来
class Sem
{
public:Sem(int value){//为0代表着多线程的同步,值大于0表示可以共享,用于多个相关进程的同步sem_init(&sem_,0,value);}~Sem(){sem_destroy(&sem_);}void p(){//申请信号量资源,如果申请失败,就阻塞sem_wait(&sem_);}void v(){//信号量本质就是一个计数器,这个函数就是让计数器++sem_post(&sem_);}private:sem_t sem_;
};
const int g_default_num = 5;template <class T>
class RingQueue
{
public:RingQueue(int default_num = g_default_num): ring_queue_(default_num), num_(default_num), c_step(0), p_step(0), space_sem_(default_num), data_sem_(0){pthread_mutex_init(&clock, nullptr);pthread_mutex_init(&plock, nullptr);}~RingQueue(){pthread_mutex_destroy(&clock);pthread_mutex_destroy(&plock);}//这里注意是先去进行P操作,再去加锁void push(const T &in){space_sem_.p();pthread_mutex_lock(&plock);ring_queue_[p_step++] = in;p_step %= num_;pthread_mutex_unlock(&plock);data_sem_.v();}void pop(T *out){data_sem_.p();pthread_mutex_lock(&clock);*out = ring_queue_[c_step++]; // pop数据后,生产者就多了一个资源c_step %= num_;pthread_mutex_unlock(&clock);space_sem_.v();}private:std::vector<T> ring_queue_;int num_; // 有多少空间int c_step; // 消费者下标int p_step; // 生产者下标Sem space_sem_; // 空间资源Sem data_sem_; // 数据资源pthread_mutex_t clock; // 消费者的锁pthread_mutex_t plock; // 生产者的锁
};
void *consumer(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (1){sleep(1);int x;rq->pop(&x);// 进行一定的处理 -- 不要忽略它的时间消耗问题std::cout << "消费: " << x << " [" << pthread_self() << "]" << std::endl;}
}void *productor(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (1){int x = rand() % 100 + 1;rq->push(x);std::cout << "生产: " << x << " [" << pthread_self() << "]" << std::endl;}
}int main()
{srand((uint64_t)time(nullptr));RingQueue<int> *rq = new RingQueue<int>();pthread_t c[5], p[3]; // 生产者和消费者for (int i = 0; i < 5; i++){pthread_create(&c[i], nullptr, consumer, (void *)rq);}for (int i = 0; i < 3; i++){pthread_create(&p[i], nullptr, productor, (void *)rq);}for (int i = 0; i < 5; i++){pthread_join(c[i], nullptr);}for (int i = 0; i < 3; i++){pthread_join(p[i], nullptr);}return 0;
}