书接上回。
上一篇里,我们设计并实现了环形队列的数据结构。同时留下了下面的更具复杂性的问题:
- 对于push的操作,需要操作前等待队列已经有了空间,也就是说队列没有满的状态。等到这个状态出现了,才继续进行push的操作,否则,push操作挂起。
- 对于get 的操作,需要操作前等待队列有了数据,也就是说队列不为空的状态。等到这个状态出现了,才继续进行get的操作,否则,get操作挂起。
- 上面的push操作/get操作一般是不同的进程和线程。同时,也有可能有多个push的操作和get操作的进程和线程。
以上分析,可以看到,push和get都有一个等待满足条件的等待状态。
- push等待的条件是队列为有空闲的状态,当get操作后,就会腾出队列空间,从而可能满足push的条件。那么,get操作后,就需要通知push,让push从等待转入测试判断,如果条件满足,则会进行push的操作。
- 另一方面,get等待的条件是队列为有数据的的状态,当put操作后,就会将数据插入到队列中,从而可能满足get的条件。那么,push操作后,就需要通知get,让get从等待转入测试判断,如果条件满足,则会进行get的操作。
上面的push和get的等待问题都可以归结为资源生产和消费的问题。
设资源数为nresource。
资源生产方进行下面的流程:
- 资源生产操作;
- nresource加一;
- 通知资源消费方。
资源消费方则为下面的流程:
- 等待来自于生产方的通知;
- 判断是否资源数 nresource大于零,如果条件不满足,回到上一条;
- 进行资源消费的操作;
- nresource减一。
这里,这个资源生产和消费的核心技术是等待和通知技术。使用System V的信号灯以及Posix的信号灯技术可以实现这个功能。在这里,我选择另一个更高效的线程锁和条件变量的技术来实现。
具体的实现的程序的架构:
-
定义变量:
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- int nresouce;
- 进行变量的初始化,nresource设置为资源的初始数目。例如,对于队列的put操作,其初始值为队列的大小,而对于队列的get操作,初始值应为0。
-
生产方:
- pthread_mutex_lock(&mutex);
- produce_resource(); // 产生资源
- nresource++;
- pthread_cond_signal(&cond);
- pthread_mutex_unlock(&mutex);
-
消费方:
- pthread_mutex_lock(&mutex);
- while ( nresource < 1 )
- pthread_cond_wait(&cond, &mutex);
- consume_resource(); // 消费资源
- nresource--;
- pthread_mutex_unlock(&mutex);
-
-
- pthread_mutex_lock及pthread_mutex_unlock是一对加锁和解锁的过程,用于界定临界区范围。
- pthread_cond_signal会唤醒在同样的参数cond下调用pthread_cond_wait的休眠的线程。
- 显然,pthread_cond_wait是使线程进入休眠状态,等待来自于pthread_cond_signal的唤醒。它们靠同样的参数cond联系在一起。
在我们这个实际的环形队列的方案中,push及get实际上是互相的生产者和消费者。push为get生产队列数据,get消费队列数据;而get为push生产队列空间的资源,push消费队列空间资源。