- 无界队列支持以下选择:
- 单个/多个生产者
- 单个/多个消费者
- 阻塞/自旋等待
- 无等待、定时
- 生产者操作永远不会失败或等待(除非内存不够)
- 适用场景:
- 小型有界队列会在高并发下导致死锁或性能下降
- 不需担心队列过度增长
- 不适用场景
- 有队列过度增长的风险,并且允许较大的边界,可以用 DynamicBoundedQueue
- 元素入队列时不能分配空间,或必须有较小的边界,可以用fixed-size MPMCQueue 或者ProducerConsumerQueue
1、生产者与消费者
struct Consumer {
Atom<Segment*> head;
Atom<Ticket> ticket;
hazptr_obj_cohort<Atom> cohort;
explicit Consumer(Segment* s) : head(s), ticket(0) {
s->set_cohort_no_tag(&cohort); // defined in hazptr_obj
}
};
struct Producer {
Atom<Segment*> tail;
Atom<Ticket> ticket;
explicit Producer(Segment* s) : tail(s), ticket(0) {}
};
// 内部定义了一个生产者一个消费者结构,分别管理队列尾部和头部
alignas(Align) Consumer c_;
alignas(Align) Producer p_;
2、元素入队列
FOLLY_ALWAYS_INLINE void enqueue(const T& arg) {
enqueueImpl(arg);
}
……
template <typename Arg>
FOLLY_ALWAYS_INLINE void enqueueImpl(Arg&& arg) {
if (SPSC) { // 单个生产者消费者
Segment* s = tail();
enqueueCommon(s, std::forward<Arg>(arg));
} else {
// Using hazptr_holder instead of hazptr_local because it is
// possible that the T ctor happens to use hazard pointers.
hazptr_holder<Atom> hptr;
Segment* s = hptr.get_protected(p_.tail);
enqueueCommon(s, std::forward<Arg>(arg));
}
}
……
/** enqueueCommon */
template <typename Arg>
FOLLY_ALWAYS_INLINE void enqueueCommon(Segment* s, Arg&& arg) {
Ticket t = fetchIncrementProducerTicket(); // 增加生产者ticket+1
if (!SingleProducer) {
s = findSegment(s, t); // 这一步保证新的ticket有一个隶属的segment,每个segment大小segmentsize,t大于min+segmentsize,则会新增一个segment,min也随之增加。
}
DCHECK_GE(t, s->minTicket()); // t应该大于等于当前segment的 minticket
DCHECK_LT(t, s->minTicket() + SegmentSize); // t应该小于当前segmment的末尾
size_t idx = index(t); // 找到合适的segment下标
Entry& e = s->entry(idx); // 根据下标idx找到segment
e.putItem(std::forward<Arg>(arg)); // 在当前节点entry的item_位置创建元素,调用信号量成员flag_ 的post函数
if (responsibleForAlloc(t)) { // 当前ticket是segment的第一个元素,为了保证后面由空余segment,多分配一个segment
allocNextSegment(s);
}
if (responsibleForAdvance(t)) { // 当前ticket是所在segment的末尾,生产者队列的tail 要指向下一个segment的开头
advanceTail(s);
}
}
3、元素出队列
FOLLY_ALWAYS_INLINE T dequeue() noexcept {
return dequeueImpl();
}
……
FOLLY_ALWAYS_INLINE T dequeueImpl() noexcept {
if (SPSC) {
Segment* s = head();
return dequeueCommon(s);
} else {
// Using hazptr_holder instead of hazptr_local because it is
// possible to call the T dtor and it may happen to use hazard
// pointers.
hazptr_holder<Atom> hptr;
Segment* s = hptr.get_protected(c_.head);
return dequeueCommon(s);
}
}
……
/** dequeueCommon */
FOLLY_ALWAYS_INLINE T dequeueCommon(Segment* s) noexcept {
Ticket t = fetchIncrementConsumerTicket(); // 消费者队列的ticket自增1
if (!SingleConsumer) {
s = findSegment(s, t); // 找到对应的segment
}
size_t idx = index(t);
Entry& e = s->entry(idx);
auto res = e.takeItem(); // 获取当前元素
if (responsibleForAdvance(t)) { // 当前位置是segment的末尾,则将消费者的head指向下个segment的开头
advanceHead(s);
}
return res;
}
这里无界队列的实现有点类似STL 的 dequeue,生产证在队列尾部添加元素,消费者在队列头部消费元素,整个队列由多个segment构成,利用了数组和链表的优势。