日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

环形队列出队的元素怎么输出出来_队列:队列在线程池等有限资源池中的应用...

發布時間:2023/12/10 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 环形队列出队的元素怎么输出出来_队列:队列在线程池等有限资源池中的应用... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們知道,CPU資源是有限的,任務的處理速度與線程個數并不是線性正相關的。相反,過多的線程反而會導致CPU頻繁切換,處理性能下降。所以,線程池的大小一般都是綜合考慮要處理任務的特點和硬件環境,來事先設置的。

當我們向固定大小的線程池中請求一個線程時,如果線程池沒有空閑資源了,這個時候線程池如何處理這個請求?是拒絕請求還是排隊請求?各種處理策略又是怎么實現的呢?

實際上,這些問題并不復雜,其底層的數據結構就是我們今天要學的內容,隊列(queue)。

如果理解“隊列”

隊列這個概念非常好理解。你可以把它想象成排隊買票,先來的先買,后來的人只能站末尾,不允許插隊。先進來先出,這就是典型的“隊列”。

我們知道,棧只支持兩個基本操作:入棧push()和出棧pop()。隊列跟棧非常相似,支持的操作也很有限,最基本的操作也是兩個:入隊enqueue(),放一個數據到隊列尾部;出列dequeue(),從隊列頭部取一個元素。

所以,隊列跟棧一樣,也是一種操作受限的線性表數據結構。

隊列的概念很好理解,基本操作也很容易掌握。作為一種非?;A的數據結構,隊列的應用也非常廣泛,特別是一些具有某些額外特性的隊列,比如循環隊列,阻塞隊列,并發隊列。他們在很多偏底層系統,框架,中間件的開發中,起著關鍵性的作用。比如高性能隊列Disruptor,Linux環形緩存,都用到循環并發隊列;Java concurrent并發包利用ArrayBlockingQueue來實現公平鎖。

順序隊列和鏈式隊列

我們知道了,隊列和棧一樣,也是一種抽象的數據結構。它具有先進先出的特性,支持在隊尾插入元素,在對頭刪除元素,那究竟該如何實現一個隊列呢?

跟棧一樣,隊列可以用數組來實現,也可以用鏈表來實現。用數組實現的棧叫順序棧,用鏈表實現的棧叫鏈式棧。同樣,用數組實現的隊列叫作順序隊列,用鏈表實現的隊列叫作鏈式隊列。

我們先來看下基于數組的實現方法。我用java語言實現了一下,不過并不包含Java語言的高級語法,而且我做了比較詳細的注釋,你應該可以看懂。

//用數組實現的隊列

比起棧的數組實現,隊列的數組實現稍微有點兒復雜,但是沒關系。我稍微解釋一下實現思路,你很容易就能明白了。

對于棧來說,我們只需要一個棧頂指針就可以了。但是隊列需要兩個指針:一個是head指針,指向對頭;一個是tail指針,指向隊尾。

你可以結合下面這幅圖來理解。當a,b,c,d依次入隊之后,隊列中的head指針指向下標為0的位置,tail指針指向下標為4的位置。

當我們調用兩次出隊操作之后,隊列中head指針指向下標為2的位置,tail指針仍然指向下標為4的位置。

你肯定已經發現了,隨著不停地進行入隊,出隊操作,head和tail都會持續往后移動。當tail移動到最右邊,即使數組中還有空閑空間,也無法繼續往隊列中添加數據了。這個問題該如何解決呢?

你是否還記得,在數組那一節,我們也遇到過類似的問題,就是數組的刪除操作會導致數組中的數組不連續。你還記得我們當時是怎么解決的嗎?對用數據搬移!但是,每次進行出隊操作都相當于刪除數組下標為0的數據,要搬移整個隊列中的數據,這樣出隊操作的時間復雜度就會從原來的O(1)變成O(n)。能不能優化一下呢?

實際上,我們在出隊時可以不用搬移數據。如果沒有空閑空間了,我們只需要在入隊時,再集中觸發一次數據的搬移操作。借助這個思想,出隊函數dequeue()保持不變,我們稍加改造一下函數enqueue()的實現,就可以輕松解決剛才的問題了。

下面是具體的代碼:

//入隊操作,將item放入隊尾

從代碼中我們看到,當隊列的tail指針移動到數組的最右邊后,如果有新的數據入隊,我們可以將head到tail之間的數據,整體搬移到數組中0到tail-head的位置。

這種實現思路中,出隊操作的時間復雜度仍然是O(1),但入隊操作的時間復雜度還是O(1)嗎?你可以用我們第3節,第4節講的算法復雜度分析方法,自己試著分析一下。

接下來,我們再來看下基于鏈表的隊列實現方法。

基于鏈表的實現,我們同樣需要兩個指針:head指針和tail指針。他們分別指向鏈表的第一個結點和最后一個結點。如圖所示,入隊是,tail->next=new_node,tail = tail->next;出隊時,head = head->next。

循環隊列

我們剛才用數組來實現隊列的時候,在tail == n時,會有數據搬移操作,這樣入隊操作性能就會收到影響。那有沒有辦法能夠避免數據搬移呢?我們來看看循環隊列的解決思路。

循環隊列,顧名思義,它長得像一個環。原本數組是有頭有尾的,是一條直線?,F在我們把首尾相連,扳成了一個環。我畫了一張圖,你可以直觀地感受一下。

我們可以看到,圖中這個隊列的大小為8,當前head=4,tail=7。當有一個新的元素a入隊時,我們放入下標為7的位置。但這個時候,我們并不把tail更新為8,而是將其在環中后移一位,到下標為0的位置。當再有一個元素b入隊時,我們將b放入下標為0的位置,然后tail加1更新為1。所以,在a,b依次入隊之后,循環隊列中的元素就變成了下面的樣子:

通過這樣的方法,我們成功避免了數據搬移操作??雌饋聿浑y理解,但是循環隊列的代碼實現難度要比前面講的非循環隊列難多了。要想寫出沒有bug的循環隊列的實現代碼,我個人覺得,最關鍵的是,確定好對空和隊滿的判定條件。

在用數組實現的非循環隊列中,隊滿的判斷條件是tail ==n,隊空的判斷條件是head==tail。那針對循環隊列,如何判斷隊空和隊滿呢?

隊列為空的判斷條件仍然是head == tail。但隊列滿的判斷條件就稍微有點復雜了。我畫了一張隊列滿的圖,你可以看一下,試著總結一下規律。

就像我圖中畫的隊滿的情況,tail=3,head=4,n=8,所以總結一下規律就是:(3+1)%8=4。

多畫幾張隊滿 的圖,你就會發現,當隊滿時,(tail+1)%n=head。

你有沒有發現,當隊列滿時,圖中的tail指向的位置實際上是沒有存儲數據的。所以,循環隊列會浪費一個數組的存儲空間。

Taik is cheap,如果還是沒怎么理解,那就show 有code吧。

public

阻塞隊列和并發隊列

前面講的內容理論比較多,看起來很難跟實際的項目開發扯上關系。確實,隊列這種數據結構很基礎,平時的業務開發不大可能從零實現一個隊列,甚至都不會直接用到。而一些具有特殊特性的隊列應用卻比較廣泛,比如阻塞隊列和并發隊列。

阻塞隊列其實就是在隊列基礎上增加了阻塞操作。簡單來說,就是在隊列為空的時候,從隊頭取數據會被阻塞。因為此時還沒有數據可取,直到隊列中有了數據才能返回;如果隊列已經滿了,那么插入數據的操作就會阻塞,直到隊列中有空閑位置后再插入數據,然后再返回。

你應該已經發現了,上述的定義就是一個“生產者-消費者模型!”是的,我們可以使用阻塞隊列,輕松實現一個“生產者-消費者模型”!

這種基于阻塞隊列實現的“生產者-消費者模型”,可以有效地協調生產和消費的速度。當“生產者”生產數據的速度過快,“消費者”來不及消費時,存儲數據的隊列很快就會滿了。這個時候,生產者阻塞等待,知道“消費者”消費了數據,“生產者”才會被喚醒繼續“生產”。

而且不僅如此,基于阻塞隊列,我們還可以通過協調“生產者”和“消費者”的個數,來提高數據的處理效率。比如前面的例子,我們可以配置幾個“消費者”,來應對一個“生產者”。

前面我們講了阻塞隊列,在多線程情況下,會有多個線程同時操作隊列,這個時候就會存在線程安全問題,那如何實現一個線程安全的隊列呢?

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

解答開篇

隊列的知識就講完了,我們現在回過來看下開篇的問題。線程池沒有空閑線程時,新的任務請求線程資源時,線程池該如何處理?各種處理策略又是如何現實的呢?

我們一般有兩種處理策略。第一種是非阻塞的處理方式,直接拒絕任務請求;另一種是阻塞的處理方法,將請求排隊,等到有空閑線程時,取出排隊的請求繼續處理。那如何存儲排隊的請求呢?

我們希望公平地處理每個排隊的請求,先進者先服務,所以隊列這種數據結構很適合來存儲排隊請求。我們前面說過,隊列有基于鏈表和基于數組這兩種實現方式。這兩種實現方式對于排隊請求又有什么區別呢?

基于鏈表的實現方式,可以實現一個支持無限排隊的無界隊列(unbounded queue),但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以,針對響應時間比較敏感的系統,基于鏈表實現的無限排隊的線程池是不合適的。

而基于數組實現的有界隊列(bounded queue),隊列的大小有限,所以線程池中隊列的請求超過隊列大小時,接下來的請求就會被拒絕,這種方式對響應敏感的系統來說,就相對更加合理。不過,設置一個合理的隊列大小,也是非常有講究的。隊列太大導致等待的請求太多,隊列太小會導致無法充分利用系統資源,發揮最大性能。

除了前面講到隊列應用在線程池請求隊列的場景之外,隊列可以應用在任何有限資源池中,用于排隊請求,比如數據庫連接池等。實際上,對于大部分資源有限的場景,當沒有空閑資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。

內容小結

今天我們講 了一種跟棧很相似的數據結構,隊列。關于隊列,你能掌握下面的內容,這節就沒問題了。

隊列最大的特點就是先進先出,主要的兩個操作是入隊和出隊。跟棧一樣,它既可以用數組來實現,也可以用鏈表來實現。用數組實現的叫順序隊列,用鏈表實現的叫鏈式隊列。特別是長得像一個環的循環隊列。在數組實現隊列的時候,會有數組搬移操作,要想解決數據搬移的問題,我們就需要像環一樣的循環隊列。

循環隊列是我們這節的重點。要想寫出沒有bug的循環隊列實現代碼,關鍵要確定好隊空和隊滿的判定條件,具體的代碼你要能寫出來。

除此之外,我們還講了幾種高級隊列結構,阻塞隊列,并發隊列,底層都還是隊列這種數據結構,只不過在之上附加了很多其他功能。阻塞隊列就是入隊,出隊操作可以阻塞,并發隊列就是隊列的操作多線程安全。

參考文獻

王爭老師 《數據結構與算法之美》

總結

以上是生活随笔為你收集整理的环形队列出队的元素怎么输出出来_队列:队列在线程池等有限资源池中的应用...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。