之前说的是单生产者单消费者模型,通过信号量处理 生产者和消费者之间的同步关系,保证了生产者生产多少,消费者就可以消费多少的平衡状态。避免出现消费者消费的数目大于生产者生产的数目。
以此为基础,接下来需要实现的是多生产者多消费者模型,既然是多生产者,就说明要处理的是生产者线程之间的竞争关系,那么到底应该由谁先生产呢??解决这个问题的就是互斥锁了。
目录
一、先申请信号量 or 先加锁
以生产者线程为例,所有的线程都会访问Push函数,_container就是一份临界资源,所以我们要其进行加锁,那么应该是先申请信号量 还是 先加锁??==》建议先申请信号量
假设我们是先申请锁,再申请信号量。如果申请到锁了,那么每次就只有一个执行流可以申请信号量,此时若申请信号量失败了(比如信号量这个计数器已经到 0 了),此时就会申请失败,申请失败的线程会被挂起,注意这个线程是抱着锁挂起的!那么在等到其他线程释放信号量之前,所有的线程都无法使用锁,也就是无法生产。
所以最佳方案是先申请信号量,再加锁。要进入高铁站坐车,就需要先预定票,然后乘客一个个进入站内。
二、代码修改
我们下面的代码只需要基于上一次的单生产者单消费者模型修改即可。
1、成员变量
所有的生产者线程或者消费者线程都要调用Push/Pop函数,为了保证生产或者消费的原子性,我们采用加锁的方式保证每一次只有一个线程可以生产或者消费。
2、构造函数、析构函数
既然添加了锁,那就必须要有对应的初始化锁和销毁锁
3、Push函数
虽然信号量也是临界资源,但是信号量的申请和释放过程是原子性的。现在就是每个线程先申请到信号量,申请到信号量以后,就是申请锁,即便申请失败,也不会抱着锁挂起。
4、Pop函数
Pop函数的做法同Push函数
三、小总结
至此,现在已经了解了基于阻塞队列的生产者消费者模型、基于环形队列的生产者消费者模型。在者之前,我一直以为生产者和消费者分别需要一把互斥锁,用来维护生产者线程之间的互斥和消费者线程之间的互斥。
在“基于阻塞队列的生产者消费者模型”中,互斥锁始终只有一把,因为要维护的对象只有一个,那就是 _container,存放数据的容器。
在“基于环形队列的生产者消费者模型”中,互斥锁有两把,是因为信号量有两个信号量也是临界资源,一个是代表空白位置的信号量,一个是代表放了多少个数据的信号量。所以需要两把互斥锁分别维护这两个临界资源。