【數據結構與算法之美】隊列:隊列在線程池等有限資源池中的應用

目錄

一、如何理解“隊列”?

二、順序隊列和鏈式隊列

三、循環隊列

四、阻塞隊列和併發隊列

五、線程池滿時,如何處理新的任務請求?有什麼處理策略?

六、課後思考


一、如何理解“隊列”?

1、隊列是一種操作受限的線性表數據結構。
2、隊列最大的特點就是先進先出。
3、最基本的操作:入隊 enqueue(),放一個數據到隊列尾部;出隊 dequeue(),從隊列頭部取一個元素。

二、順序隊列和鏈式隊列

1、用數組實現的隊列叫順序隊列,用鏈表實現的隊列叫鏈式隊列。
2、隊列需要兩個指針:一個是 head 指針,指向隊頭;一個是 tail 指針,指向隊尾。
3、隨着不停地進行入隊、出隊操作,head 和 tail 都會持續往後移動。當 tail 移動到最右邊,即使數組中還有空閒空間,也無法繼續往隊列中添加數據了。
       實際上,我們在出隊時可以不用搬移數據。如果沒有空閒空間了,只需要在入隊時,再集中觸發一次數據的搬移操作。出隊函數 dequeue() 保持不變,我們稍加改造一下入隊函數 enqueue() 的實現,當隊列的 tail 指針移動到數組的最右邊後,如果有新的數據入隊,我們可以將 head 到 tail 之間的數據,整體搬移到數組中 0 到 tail-head 的位置。
4、基於鏈表的實現,同樣需要兩個指針:head 指針和 tail 指針。分別指向鏈表的第一個結點和最後一個結點。入隊時,tail->next= new_node, tail = tail->next;出隊時,head = head->next。

三、循環隊列

1、循環隊列,原本數組是有頭有尾的,是一條直線。把首尾相連,扳成了一個環。
2、在數組實現隊列的時候,會有數據搬移操作,要想解決數據搬移的問題,需要像環一樣的循環隊列。
3、要想寫出沒有 bug 的循環隊列的實現代碼,最關鍵的是,確定好隊空和隊滿的判定條件。
1)隊列爲空的判斷條件仍然是 head == tail。
2)當隊滿時,(tail+1)%n=head。 tail 指向的位置實際上是沒有存儲數據的。所以,循環隊列會浪費一個數組的存儲空間。

四、阻塞隊列和併發隊列

1、阻塞隊列
1)阻塞隊列就是在隊列基礎上增加了阻塞操作。
2)在隊列爲空的時候,從隊頭取數據會被阻塞。因爲此時還沒有數據可取,直到隊列中有了數據才能返回;如果隊列已經滿了,那麼插入數據的操作就會被阻塞,直到隊列中有空閒位置後再插入數據,然後再返回。
3)基於阻塞隊列實現的“生產者 - 消費者模型”,可以有效地協調生產和消費的速度。

2、併發隊列
1)線程安全的隊列,叫作併發隊列。
2)最簡單直接的實現方式是直接在 enqueue()、dequeue() 方法上加鎖,但是鎖粒度大併發度會比較低,同一時刻僅允許一個存或者取操作。
3)實際上,基於數組的循環隊列,利用 CAS 原子操作,可以實現非常高效的併發隊列。這也是循環隊列比鏈式隊列應用更加廣泛的原因。

五、線程池滿時,如何處理新的任務請求?有什麼處理策略?

一般有兩種處理策略。第一種是非阻塞的處理方式,直接拒絕任務請求;另一種是阻塞的處理方式,將請求排隊,等到有空閒線程時,取出排隊的請求繼續處理。
1、基於鏈表的實現方式,可以實現一個支持無限排隊的無界隊列(unbounded queue),但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以,針對響應時間比較敏感的系統,基於鏈表實現的無限排隊的線程池是不合適的。
2、基於數組實現的有界隊列(bounded queue),隊列的大小有限,所以線程池中排隊的請求超過隊列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統來說,就相對更加合理。不過,設置一個合理的隊列大小,也是非常有講究的。隊列太大導致等待的請求太多,隊列太小會導致無法充分利用系統資源、發揮最大性能。
(除了前面講到隊列應用在線程池請求排隊的場景之外,隊列可以應用在任何有限資源池中,用於排隊請求,比如數據庫連接池等。實際上,對於大部分資源有限的場景,當沒有空閒資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。)

六、課後思考

1. 除了線程池這種池結構會用到隊列排隊請求,還有哪些類似的池結構或者場景中會用到隊列的排隊請求呢?

1)像windows操作系統的消息隊列,略高級一些帶有優先級。還有qt中的信號與槽函數機制,使用connect鏈接,其中的參數就是設置爲把窗口界面消息放到消息隊列,然後一次取出。比如優先級消息,窗口系統關閉,優先級高,則就直接執行關閉操作。
2)sockets網絡連接隊列。
3)數據庫連接隊列。
4)一種集羣操作,很多客戶端像服務端請求資源,處理高併發大量請求,把這些請求放到隊列中。
5)分佈式應用中的消息隊列如rabbitMQ、rocketmq、kafka等,也是一種隊列結構。

2. 如何實現無鎖併發隊列?
考慮使用CAS實現無鎖隊列,則在入隊前,獲取tail位置,入隊時比較tail是否發生變化,如果否,則允許入隊,反之,本次入隊失敗。出隊則是獲取head位置,進行cas。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章