原文轉自:http://www.tanjp.com/archives/124 (即時修正和更新)
分組安全隊列 - 非嚴格優先隊列 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_frame_count, 過幀數量,過幀到達指定該數量將會把元素提高到最高優先級。0表示不進行提高處理。
// @pn_maxsize, 容器元素數量上限。
explicit SafeGroupQueue(bool pb_block = true, uint8 pn_group_size = kDefaultGroupSize,
uint32 pn_upgrade_frame_count = kDefaultUpgradeFrame, uint32 pn_maxsize = kDefaultMaxsize)
: mn_group_size(pn_group_size > 1 ? pn_group_size : kDefaultGroupSize)
, mn_upgrade_frame_count(pn_upgrade_frame_count)
, mb_block(pb_block)
, mn_frame(0)
, mn_maxsize(pn_maxsize > 0 ? pn_maxsize : kDefaultMaxsize)
, mc_group_queue()
, mc_frame_queue()
, mn_count(0)
, mn_first_pop_group(0)
, mb_push_waiting(0)
, mb_pop_waiting(0)
{
for (uint8 i = 0; i < mn_group_size; ++i)
{
mc_group_queue.push_back(QueueType(new std::deque<FrameDataType>()));
}
}
~SafeGroupQueue() { }
// 加入元素到指定分組隊列的尾部
// @po_val, 待加入的元素。
// @pn_priority, 指定的分組,值越大會被優先處理,取值範圍[0,group_size-1],超過分組大小將放在最高分組。
// @return, 非阻塞模式下加入成功返回true,失敗返回false。阻塞模式下必然返回true,否則掛起等待。
bool push(const tpType & po_val, uint8 pn_priority = kDefaultPriorityValue)
{
{
std::lock_guard<std::mutex> lock(mo_mutex);
if (pn_priority >= mc_group_queue.size())
{
//越界處理
pn_priority = uint8(mc_group_queue.size() - 1);
}
while ((mn_count >= mn_maxsize) && mb_block)
{
//滿且阻塞標記中,掛起等待
++mb_push_waiting;
mo_cond_push.wait(mo_mutex);
--mb_push_waiting;
}
if ((mn_count >= mn_maxsize) && (!mb_block))
{
//滿且不阻塞標記中,馬上返回
return false;
}
++mn_count; //計數
++mn_frame; //每次加入幀數+1
if (mn_frame >= kFrameMaxValue)
{
reset_frame(); //重置幀數
}
FrameDataType val(po_val, mn_frame);
mc_group_queue[pn_priority]->push_back(val); //放入指定分組
if (pn_priority > mn_first_pop_group)
{
mn_first_pop_group = pn_priority; //優先取最高分組的數據
}
if ((mn_group_size > 1) && (mn_upgrade_frame_count > 0))
{
//記錄幀數優先級
FrameGroupType ft(pn_priority, mn_frame);
mc_frame_queue.push_back(ft);
}
} //釋放鎖
if (mb_pop_waiting > 0)
{
mo_cond_pop.notify_one();
}
return true;
}
// 取出有數據的最高分組的隊列頭部元素。還會對下個元素進行過幀升組處理。
// @po_val, 待取出的頭部元素。
// @return, 非阻塞模式下取出成功返回true,失敗返回false。阻塞模式下必然返回true,否則掛起等待。
bool pop(tpType & po_val)
{
{
std::lock_guard<std::mutex> lock(mo_mutex);
while ((mn_count <= 0) && mb_block)
{
//空且阻塞標記中,掛起等待
++mb_pop_waiting;
mo_cond_pop.wait(mo_mutex);
--mb_pop_waiting;
}
if ((mn_count <= 0) && (!mb_block))
{
//空且不阻塞標記中,馬上返回
return false;
}
--mn_count;
FrameDataType & val = mc_group_queue[mn_first_pop_group]->front();
uint64 zn_pop_frame = val.frame;
po_val = val.data;
mc_group_queue[mn_first_pop_group]->pop_front();
if (mn_count <= 0)
{
//沒數據了
mn_first_pop_group = 0;
while (!mc_frame_queue.empty())
{
//隊列已經空了
mc_frame_queue.pop_front();
}
}
else
{
if (!mc_frame_queue.empty() && (zn_pop_frame == mc_frame_queue.front().frame))
{
//同一個元素,直接丟棄,意思是已處理
mc_frame_queue.pop_front();
}
else
{
while (!mc_frame_queue.empty())
{
//找非空的隊列
FrameGroupType & ft = mc_frame_queue.front();
QueueType & zc_queue = mc_group_queue[ft.priority];
if (zc_queue->empty() || (zc_queue->front().frame > ft.frame))
{
//幀隊列爲空或頭部元素已經被處理掉,直接丟棄,意思是已處理
mc_frame_queue.pop_front();
}
else
{
int64 len = zn_pop_frame - ft.frame; //已經超過的幀數
uint8 zn_max_priority = (uint8)(mc_group_queue.size() - 1);
if ((len > mn_upgrade_frame_count) && (ft.priority < zn_max_priority))
{
//超過指定幀數沒被處理,提高優先級,移動到最高優先級的隊列中
FrameDataType & move_val = zc_queue->front();
mc_group_queue[zn_max_priority]->push_back(move_val);
zc_queue->pop_front();
ft.priority = zn_max_priority; //改變優先級到最高
mn_first_pop_group = zn_max_priority;
}
break; //每次取出,只提升一個元素優先級
}
} //while
}
while (mc_group_queue[mn_first_pop_group]->empty())
{
//找下個優先級高的隊列
--mn_first_pop_group;
if (mn_first_pop_group <= 0)
{
break;
}
} //while
} //else
} //釋放鎖
if (mb_push_waiting > 0)
{
mo_cond_push.notify_one();
}
return true;
}
bool empty()
{
std::lock_guard<std::mutex> lock(mo_mutex);
return (mn_count <= 0);
}
bool full()
{
std::lock_guard<std::mutex> lock(mo_mutex);
return (mn_count >= mn_maxsize);
}
uint32 size()
{
std::lock_guard<std::mutex> lock(mo_mutex);
return mn_count;
}
void clear()
{
{
std::lock_guard<std::mutex> lock(mo_mutex);
for (uint32 i = 0; i < mc_group_queue.size(); ++i)
{
while (!mc_group_queue[i]->empty())
mc_group_queue[i]->pop_front();
}
mc_frame_queue.clear();
mn_frame = 0;
mn_first_pop_group = 0;
mn_count = 0;
}
mo_cond_push.notify_all();
}
void set_block(bool pb_block)
{
{
std::lock_guard<std::mutex> lock(mo_mutex);
if (mb_block == pb_block)
{
//一樣的,不需要重複設置
return;
}
mb_block = pb_block;
}
mo_cond_pop.notify_all();
mo_cond_push.notify_all();
}
bool set_maxsize(uint32 pn_maxsize)
{
std::lock_guard<std::mutex> lock(mo_mutex);
if ((pn_maxsize < mn_count) || (pn_maxsize <= 0))
{
return false;
}
mn_maxsize = pn_maxsize;
return true;
}
uint8 group_size() const { return mn_group_size; }
protected:
//不可拷貝不可移動
SafeGroupQueue(const SafeGroupQueue&) = delete;
SafeGroupQueue(SafeGroupQueue&&) = delete;
SafeGroupQueue& operator=(const SafeGroupQueue&) = delete;
SafeGroupQueue& operator=(SafeGroupQueue&&) = delete;
//幀數過大,重置幀數
void reset_frame()
{
// 幀數太大,消減 9/10
const uint64 zn_sub_frame = mn_frame - (mn_frame / 10U);
mn_frame -= zn_sub_frame;
for (auto it = mc_frame_queue.begin(); it != mc_frame_queue.end(); ++it)
{
FrameGroupType & fgt = *it;
fgt.frame -= zn_sub_frame;
}
for (auto it_vec = mc_group_queue.begin(); it_vec != mc_group_queue.end(); ++it_vec)
{
QueueType & zc_group = *it_vec;
for (auto it_queue = zc_group->begin(); it_queue != zc_group->end(); ++it_queue)
{
FrameDataType & fdt = *it_queue;
fdt.frame -= zn_sub_frame;
}
}
}
protected:
const uint8 mn_group_size; //組大小
const uint32 mn_upgrade_frame_count; //超過幀數沒執行,提高優先級,0表示無效
bool mb_block;
uint64 mn_frame; //幀數,每次加入時+1
uint32 mn_maxsize;
std::vector<QueueType> mc_group_queue; //分組隊列
std::deque<FrameGroupType> mc_frame_queue; //幀隊列
uint32 mn_count;
uint8 mn_first_pop_group; //當前已取出的組
std::mutex mo_mutex;
std::condition_variable_any mo_cond_push;
std::condition_variable_any mo_cond_pop;
std::atomic<int32> mb_push_waiting;
std::atomic<int32> mb_pop_waiting;
};
應用示例:
int main(int argc, char* argv[])
{
const uint8 kGroupSize = 4;
const uint32 kUpgradeFrameNum = 20;
SafeGroupQueueType zc_group_queue(false, kGroupSize, kUpgradeFrameNum);
std::cout << "input : " << std::endl;
for (uint32 i = 0; i < 10; ++i)
{
uint32 zn_val = i + 1;
zc_group_queue.push(zn_val, 0);
std::cout << zn_val << ", ";
}
for (uint32 i = 10; i < 20; ++i)
{
uint32 zn_val = i + 1;
zc_group_queue.push(zn_val, 1);
std::cout << zn_val << ", ";
}
for (uint32 i = 20; i < 30; ++i)
{
uint32 zn_val = i + 1;
zc_group_queue.push(zn_val, 2);
std::cout << zn_val << ", ";
}
std::cout << std::endl;
std::cout << "output : " << std::endl;
while (true)
{
uint32 zn_val = 0;
if (zc_group_queue.pop(zn_val))
{
std::cout << zn_val << ", ";
}
else
{
break;
}
}
std::cout << std::endl;
return 0;
}
輸出結果爲
input :
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
output :
21, 22, 1, 23, 2, 24, 3, 25, 4, 26, 5, 27, 6, 28, 7, 29, 8, 30, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 10,
超過 20 幀的元素 1, 2, 3, 4, 5, 6, 7, 8, 9 都被提高優先級處理。