原文转自:http://www.tanjp.com (即时修正和更新)
分组安全队列 - 非严格优先队列 O(1) 时间复杂度
传统的优先队列,都是采用二叉树(堆)的方式来实现,插入和删除都需要维护堆顶元素最大或最小,需要O(logN)的时间复杂度。在某些多线程环境下,把队列当作一个数据交换的纽带,这种开销也是非常大的,于是设计了非严格的优先队列,基满足业务需求,同时插入和取出时间复杂度为O(1)。
把队列分为 N 个组,组的编号范围[0, N-1],push时能加入到指定的分组上(也就是能指定优先级),pop时编号越大的组会优先取出(编号越大优先级越高)。
如果只是这样的规则,可能会导致低优先级的元素永远无法取出。
于是,设计了帧的概念,帧数从0开始,每次push时,帧数加一,并记录在一个帧队列里面。当调用pop取出元素后,判断当前组头部元素所属的帧与帧队列头的帧比较,超过指定帧数F时,就把该头部元素加入到最高编号(最高优先级)的组的尾部,也就是说下次会优先从最高组取出(但不一定马上取出,因为是加到最高组的最后)。
概括来说,这是一个不严格的优先队列,当太久前加入的元素没被取出,则提升其优先级,尽可能均衡,而不导致的元素积压过久。这样做避免传统树形结构的优先队列,在每次 push和pop都需要 O(logN)时间复杂度,来调整树的平衡。
分组安全队列的push和pop基本是O(1)的时间复杂度。push的时候只要找到相应的组把元素加入进去并记录在帧队列中就行,时间复杂度O(1)。pop的时候,取出有数据的最高组的头部元素,并比较取出元素的帧数与当前帧队列头部元素的帧数,超过指定值,就触发提高优先级操作,时间复杂度也是O(1)。所以在性能上有很大的优势,当然要根据需求是否能采用这种非严格的优先处理方式。
以下是实现代码:
template<typename tpType>
class SafeGroupQueue
{
static const uint64 kFrameMaxValue = (std::numeric_limits<uint64>::max() - 1000U); //超过此帧数将会重置
static const uint32 kDefaultMaxsize = 4294967290U; //队列容量上限
static const uint32 kDefaultUpgradeFrame = 8U; //默认优先处理的帧数
static const uint8 kDefaultGroupSize = 5U; //优先队列默认分级大小
static const uint8 kDefaultPriorityValue = 2U; //优先队列默认加入分级
struct FrameDataType
{
FrameDataType(const tpType & po_data, uint64 pn_frame)
: data(po_data)
, frame(pn_frame) {}
tpType data;
uint64 frame;
};
struct FrameGroupType
{
FrameGroupType(uint8 pn_priority, uint64 pn_frame)
: priority(pn_priority)
, frame(pn_frame) {}
uint8 priority;
uint64 frame;
};
typedef std::shared_ptr< std::deque<FrameDataType> > QueueType;
public:
// @pb_block, true为阻塞模式,push和pop操作在边界条件下将会阻塞等待。
// false为非阻塞操作,push和pop操作边界条件将会立即返回false。
// @pn_group_size, 组的大小,取值范围[2, 255]。
// @pn_upgrade_fr