队列的异常安全
考虑一下 STL 中 queue 的接口:
我们知道,front()
用来获得队列头部元素,而pop()
则令头部元素出列。但是为什么不设计成这样呢:
其实这是为了保证异常安全(Exception Safe),像下面这个例子:
设想一下,要是top()
将元素出列,并且将这个元素赋值给 value 时,若其拷贝构造函数发生了异常,那么,这个元素就会永远丢失了。
关于实现的细节
我们将设计一个 ThreadQueue,并且具有以下的特点:
- 允许多个读者 ( Reader ) 和多个写者 ( Writer ) 并发地访问队列。
- 元素出列的操作将会阻塞,直到队列不为空。
- 元素出列时保证异常安全性。
我们知道,在调用std::queue<T>
的front()
之前,我们需要保证队列不为空,否则这种行为是未定义的。但是在多线程环境下,我们通常不需要front()
操作,而只是调用pop()
将元素返回。pop()
操作是阻塞的,也就是说,在调用pop()
时,要是队列没有元素,那么pop()
将会阻塞,以等待新元素入列:
但是,我们知道,pop()
并不是异常安全的,那怎么实现异常安全的pop()
呢?
在调用pop()
之前,我们需要传递一个元素作为参数,以存储将要出列的元素:
阻塞队列的实现
阻塞队列实际上就是典型的生产者-消费者模型,可想而知,应当使用条件变量:
注意到,要是队列是空的,那么pop()
操作将会阻塞,因此,我们提供了一个try_pop()
操作,要是队列是空的,调用try_pop()
将会立即返回而不会阻塞。
生产者与消费者
我们知道,ThreadQueue 可以用在多个生产者,多个消费者的场景下,例如: