0 介绍
以前只是会使用Linux下的条件变量pthread_cond_t,知道它的作用是配合互斥锁解决并发编程的同步问题,没有思考过它的实现原理,今天学习了清华大学陈渝老师的操作系统网课,总算是明白了,特此记录。
1 实现
条件变量是一种等待机制,每一个条件变量对应一个等待原因与等待队列。一般对于条件变量会有两种操作:
- wait操作 : 将自己阻塞在等待队列里,唤醒一个等待者或者开放锁的互斥访问
- singal 操作 : 唤醒一个等待的线程(等待队列为空的话什么也不做)
下面看看它的伪码描述,两个变量一个描述在等待队列的线程数,一个就是等待队列:
class Condition
{
int numWaiting = 0; //在队列中等待的线程数
WaitQueue q; //等待队列
};
对于wait操作来说,首先给numWaiting加1,然后将当前线程加入等待队列里,然后释放开锁,schedule将放开cpu给其他线程,回来之后再次尝试获取锁。
Condition::Wait(lock)
{
++numWaiting;
Add this thread to WaitQueue q;
release(lock);
schedule(); //调度机制
acquire(lock);
}
对于singal操作来说,则如果等待队列中有线程的话,将它取出唤醒,numWaiting减一即可。
Condition::Singal()
{
if(numWaiting > 0)
{
remove t from q;
wakeup(q);
--numWaiting;
}
}
2 应用
懂了这些实现后,看看如何利用前面的知识解决经典的生产者消费者问题。我们假定有多个生产者和多个消费者,生产者往buffer中加入产品,消费者从buffer中拿走产品。buffer的大小有上限,设为n,也就是说当buffer中的产品为n时生产者不能继续在buffer中添加商品,为0时消费者不能继续在buffer中拿走商品。
不难知道我们需要一个锁Lock,两个条件变量notEmpty和notFull分别用于指示消费者和生产者,count是目前buffer中的产品数。
class BoundedQueue
{
Lock lock;
count = 0;
Condition notEmpty,notFull;
};
对于生产者来说,其执行的操作如下:首先活动锁之后,检查buffer是否满了,如果满了,则执行wait。添加好商品后,执行notEmpty的singal,唤醒一个消费者线程消费。
BoundedQueue::deposit
{
acquire(lock);
while(count == n)
notFull.wait();
add c to the buffer;
++count;
notEmpty.singal();
release(lock);
}
对于消费者来说,其操作和生产者差不多:
BoundedQueue::remove()
{
acquire(lock);
while(count == n)
notEmpty.wait();
remove c from buffer;
--count;
notFull.Singal();
release(lock);
}