當前位置:首頁 » 服務存儲 » 不存儲元素的同步隊列
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

不存儲元素的同步隊列

發布時間: 2022-05-09 12:34:24

1. 數據結構中如何理解:循環隊列的每一個元素都有一個前驅和後繼

一個循環隊列就是收尾相連的隊列,如果是用數組創建的隊列,那麼就會有指向隊首和隊尾的指針,這兩個指針通過一個函數不斷的在隊列中走來走去,當隊尾那個指針指向最後了的時候,它會用某個函數(書上有的)指向最前面,所以,這隊列中可以一直指下去,沒有停斷的時候,自然就有一個前驅和後繼了。如果是指針的話,就更加形象了,因為它的最後一個結點一直是指向最後一個結點的,所以,第一個元素和最後一個元素都有前驅和後繼了,其他中間的我就不說了。

2. 面試 linux 文件系統怎樣io到底層

前言:本文主要講解LinuxIO調度層的三種模式:cfp、deadline和noop,並給出各自的優化和適用場景建議。IO調度發生在Linux內核的IO調度層。這個層次是針對Linux的整體IO層次體系來說的。從read()或者write()系統調用的角度來說,Linux整體IO體系可以分為七層,它們分別是:VFS層:虛擬文件系統層。由於內核要跟多種文件系統打交道,而每一種文件系統所實現的數據結構和相關方法都可能不盡相同,所以,內核抽象了這一層,專門用來適配各種文件系統,並對外提供統一操作介面。文件系統層:不同的文件系統實現自己的操作過程,提供自己特有的特徵,具體不多說了,大家願意的話自己去看代碼即可。頁緩存層:負責真對page的緩存。通用塊層:由於絕大多數情況的io操作是跟塊設備打交道,所以Linux在此提供了一個類似vfs層的塊設備操作抽象層。下層對接各種不同屬性的塊設備,對上提供統一的BlockIO請求標准。IO調度層:因為絕大多數的塊設備都是類似磁碟這樣的設備,所以有必要根據這類設備的特點以及應用的不同特點來設置一些不同的調度演算法和隊列。以便在不同的應用環境下有針對性的提高磁碟的讀寫效率,這里就是大名鼎鼎的Linux電梯所起作用的地方。針對機械硬碟的各種調度方法就是在這實現的。塊設備驅動層:驅動層對外提供相對比較高級的設備操作介面,往往是C語言的,而下層對接設備本身的操作方法和規范。塊設備層:這層就是具體的物理設備了,定義了各種真對設備操作方法和規范。有一個已經整理好的[LinuxIO結構圖],非常經典,一圖勝千言:我們今天要研究的內容主要在IO調度這一層。它要解決的核心問題是,如何提高塊設備IO的整體性能?這一層也主要是針對機械硬碟結構而設計的。眾所周知,機械硬碟的存儲介質是磁碟,磁頭在碟片上移動進行磁軌定址,行為類似播放一張唱片。這種結構的特點是,順序訪問時吞吐量較高,但是如果一旦對碟片有隨機訪問,那麼大量的時間都會浪費在磁頭的移動上,這時候就會導致每次IO的響應時間變長,極大的降低IO的響應速度。磁頭在碟片上尋道的操作,類似電梯調度,實際上在最開始的時期,Linux把這個演算法命名為Linux電梯演算法,即:如果在尋道的過程中,能把順序路過的相關磁軌的數據請求都「順便」處理掉,那麼就可以在比較小影響響應速度的前提下,提高整體IO的吞吐量。這就是我們為什麼要設計IO調度演算法的原因。目前在內核中默認開啟了三種演算法/模式:noop,cfq和deadline。嚴格算應該是兩種:因為第一種叫做noop,就是空操作調度演算法,也就是沒有任何調度操作,並不對io請求進行排序,僅僅做適當的io合並的一個fifo隊列。目前內核中默認的調度演算法應該是cfq,叫做完全公平隊列調度。這個調度演算法人如其名,它試圖給所有進程提供一個完全公平的IO操作環境。註:請大家一定記住這個詞語,cfq,完全公平隊列調度,不然下文就沒法看了。cfq為每個進程創建一個同步IO調度隊列,並默認以時間片和請求數限定的方式分配IO資源,以此保證每個進程的IO資源佔用是公平的,cfq還實現了針對進程級別的優先順序調度,這個我們後面會詳細解釋。查看和修改IO調度演算法的方法是:cfq是通用伺服器比較好的IO調度演算法選擇,對桌面用戶也是比較好的選擇。但是對於很多IO壓力較大的場景就並不是很適應,尤其是IO壓力集中在某些進程上的場景。因為這種場景我們需要的滿足某個或者某幾個進程的IO響應速度,而不是讓所有的進程公平的使用IO,比如資料庫應用。deadline調度(最終期限調度)就是更適合上述場景的解決方案。deadline實現了四個隊列:其中兩個分別處理正常read和write,按扇區號排序,進行正常io的合並處理以提高吞吐量。因為IO請求可能會集中在某些磁碟位置,這樣會導致新來的請求一直被合並,可能會有其他磁碟位置的io請求被餓死。另外兩個處理超時read和write的隊列,按請求創建時間排序,如果有超時的請求出現,就放進這兩個隊列,調度演算法保證超時(達到最終期限時間)的隊列中的請求會優先被處理,防止請求被餓死。不久前,內核還是默認標配四種演算法,還有一種叫做as的演算法(Anticipatoryscheler),預測調度演算法。一個高大上的名字,搞得我一度認為Linux內核都會算命了。結果發現,無非是在基於deadline演算法做io調度的之前等一小會時間,如果這段時間內有可以合並的io請求到來,就可以合並處理,提高deadline調度的在順序讀寫情況下的數據吞吐量。其實這根本不是啥預測,我覺得不如叫撞大運調度演算法,當然這種策略在某些特定場景差效果不錯。但是在大多數場景下,這個調度不僅沒有提高吞吐量,還降低了響應速度,所以內核乾脆把它從默認配置里刪除了。畢竟Linux的宗旨是實用,而我們也就不再這個調度演算法上多費口舌了。1、cfq:完全公平隊列調度cfq是內核默認選擇的IO調度隊列,它在桌面應用場景以及大多數常見應用場景下都是很好的選擇。如何實現一個所謂的完全公平隊列(CompletelyFairQueueing)?首先我們要理解所謂的公平是對誰的公平?從操作系統的角度來說,產生操作行為的主體都是進程,所以這里的公平是針對每個進程而言的,我們要試圖讓進程可以公平的佔用IO資源。那麼如何讓進程公平的佔用IO資源?我們需要先理解什麼是IO資源。當我們衡量一個IO資源的時候,一般喜歡用的是兩個單位,一個是數據讀寫的帶寬,另一個是數據讀寫的IOPS。帶寬就是以時間為單位的讀寫數據量,比如,100Mbyte/s。而IOPS是以時間為單位的讀寫次數。在不同的讀寫情境下,這兩個單位的表現可能不一樣,但是可以確定的是,兩個單位的任何一個達到了性能上限,都會成為IO的瓶頸。從機械硬碟的結構考慮,如果讀寫是順序讀寫,那麼IO的表現是可以通過比較少的IOPS達到較大的帶寬,因為可以合並很多IO,也可以通過預讀等方式加速數據讀取效率。當IO的表現是偏向於隨機讀寫的時候,那麼IOPS就會變得更大,IO的請求的合並可能性下降,當每次io請求數據越少的時候,帶寬表現就會越低。從這里我們可以理解,針對進程的IO資源的主要表現形式有兩個:進程在單位時間內提交的IO請求個數和進程佔用IO的帶寬。其實無論哪個,都是跟進程分配的IO處理時間長度緊密相關的。有時業務可以在較少IOPS的情況下佔用較大帶寬,另外一些則可能在較大IOPS的情況下佔用較少帶寬,所以對進程佔用IO的時間進行調度才是相對最公平的。即,我不管你是IOPS高還是帶寬佔用高,到了時間咱就換下一個進程處理,你愛咋樣咋樣。所以,cfq就是試圖給所有進程分配等同的塊設備使用的時間片,進程在時間片內,可以將產生的IO請求提交給塊設備進行處理,時間片結束,進程的請求將排進它自己的隊列,等待下次調度的時候進行處理。這就是cfq的基本原理。當然,現實生活中不可能有真正的「公平」,常見的應用場景下,我們很肯能需要人為的對進程的IO佔用進行人為指定優先順序,這就像對進程的CPU佔用設置優先順序的概念一樣。所以,除了針對時間片進行公平隊列調度外,cfq還提供了優先順序支持。每個進程都可以設置一個IO優先順序,cfq會根據這個優先順序的設置情況作為調度時的重要參考因素。優先順序首先分成三大類:RT、BE、IDLE,它們分別是實時(RealTime)、最佳效果(BestTry)和閑置(Idle)三個類別,對每個類別的IO,cfq都使用不同的策略進行處理。另外,RT和BE類別中,分別又再劃分了8個子優先順序實現更細節的QOS需求,而IDLE只有一個子優先順序。另外,我們都知道內核默認對存儲的讀寫都是經過緩存(buffer/cache)的,在這種情況下,cfq是無法區分當前處理的請求是來自哪一個進程的。只有在進程使用同步方式(syncread或者syncwirte)或者直接IO(DirectIO)方式進行讀寫的時候,cfq才能區分出IO請求來自哪個進程。所以,除了針對每個進程實現的IO隊列以外,還實現了一個公共的隊列用來處理非同步請求。當前內核已經實現了針對IO資源的cgroup資源隔離,所以在以上體系的基礎上,cfq也實現了針對cgroup的調度支持。總的來說,cfq用了一系列的數據結構實現了以上所有復雜功能的支持,大家可以通過源代碼看到其相關實現,文件在源代碼目錄下的block/cfq-iosched.c。1.1cfq設計原理在此,我們對整體數據結構做一個簡要描述:首先,cfq通過一個叫做cfq_data的數據結構維護了整個調度器流程。在一個支持了cgroup功能的cfq中,全部進程被分成了若干個contralgroup進行管理。每個cgroup在cfq中都有一個cfq_group的結構進行描述,所有的cgroup都被作為一個調度對象放進一個紅黑樹中,並以vdisktime為key進行排序。vdisktime這個時間紀錄的是當前cgroup所佔用的io時間,每次對cgroup進行調度時,總是通過紅黑樹選擇當前vdisktime時間最少的cgroup進行處理,以保證所有cgroups之間的IO資源佔用「公平」。當然我們知道,cgroup是可以對blkio進行資源比例分配的,其作用原理就是,分配比例大的cgroup佔用vdisktime時間增長較慢,分配比例小的vdisktime時間增長較快,快慢與分配比例成正比。這樣就做到了不同的cgroup分配的IO比例不一樣,並且在cfq的角度看來依然是「公平「的。選擇好了需要處理的cgroup(cfq_group)之後,調度器需要決策選擇下一步的service_tree。service_tree這個數據結構對應的都是一系列的紅黑樹,主要目的是用來實現請求優先順序分類的,就是RT、BE、IDLE的分類。每一個cfq_group都維護了7個service_trees,其定義如下:其中service_tree_idle就是用來給IDLE類型的請求進行排隊用的紅黑樹。而上面二維數組,首先第一個維度針對RT和BE分別各實現了一個數組,每一個數組中都維護了三個紅黑樹,分別對應三種不同子類型的請求,分別是:SYNC、SYNC_NOIDLE以及ASYNC。我們可以認為SYNC相當於SYNC_IDLE並與SYNC_NOIDLE對應。idling是cfq在設計上為了盡量合並連續的IO請求以達到提高吞吐量的目的而加入的機制,我們可以理解為是一種「空轉」等待機制。空轉是指,當一個隊列處理一個請求結束後,會在發生調度之前空等一小會時間,如果下一個請求到來,則可以減少磁頭定址,繼續處理順序的IO請求。為了實現這個功能,cfq在service_tree這層數據結構這實現了SYNC隊列,如果請求是同步順序請求,就入隊這個servicetree,如果請求是同步隨機請求,則入隊SYNC_NOIDLE隊列,以判斷下一個請求是否是順序請求。所有的非同步寫操作請求將入隊ASYNC的servicetree,並且針對這個隊列沒有空轉等待機制。此外,cfq還對SSD這樣的硬碟有特殊調整,當cfq發現存儲設備是一個ssd硬碟這樣的隊列深度更大的設備時,所有針對單獨隊列的空轉都將不生效,所有的IO請求都將入隊SYNC_NOIDLE這個servicetree。每一個servicetree都對應了若干個cfq_queue隊列,每個cfq_queue隊列對應一個進程,這個我們後續再詳細說明。cfq_group還維護了一個在cgroup內部所有進程公用的非同步IO請求隊列,其結構如下:非同步請求也分成了RT、BE、IDLE這三類進行處理,每一類對應一個cfq_queue進行排隊。BE和RT也實現了優先順序的支持,每一個類型有IOPRIO_BE_NR這么多個優先順序,這個值定義為8,數組下標為0-7。我們目前分析的內核代碼版本為Linux4.4,可以看出,從cfq的角度來說,已經可以實現非同步IO的cgroup支持了,我們需要定義一下這里所謂非同步IO的含義,它僅僅表示從內存的buffer/cache中的數據同步到硬碟的IO請求,而不是aio(man7aio)或者linux的native非同步io以及lio機制,實際上這些所謂的「非同步」IO機制,在內核中都是同步實現的(本質上馮諾伊曼計算機沒有真正的「非同步」機制)。我們在上面已經說明過,由於進程正常情況下都是將數據先寫入buffer/cache,所以這種非同步IO都是統一由cfq_group中的async請求隊列處理的。那麼為什麼在上面的service_tree中還要實現和一個ASYNC的類型呢?這當然是為了支持區分進程的非同步IO並使之可以「完全公平」做准備嘍。實際上在最新的cgroupv2的blkio體系中,內核已經支持了針對bufferIO的cgroup限速支持,而以上這些可能容易混淆的一堆類型,都是在新的體系下需要用到的類型標記。新體系的復雜度更高了,功能也更加強大,但是大家先不要著急,正式的cgroupv2體系,在Linux4.5發布的時候會正式跟大家見面。我們繼續選擇service_tree的過程,三種優先順序類型的service_tree的選擇就是根據類型的優先順序來做選擇的,RT優先順序最高,BE其次,IDLE最低。就是說,RT里有,就會一直處理RT,RT沒了再處理BE。每個service_tree對應一個元素為cfq_queue排隊的紅黑樹,而每個cfq_queue就是內核為進程(線程)創建的請求隊列。每一個cfq_queue都會維護一個rb_key的變數,這個變數實際上就是這個隊列的IO服務時間(servicetime)。這里還是通過紅黑樹找到servicetime時間最短的那個cfq_queue進行服務,以保證「完全公平」。選擇好了cfq_queue之後,就要開始處理這個隊列里的IO請求了。這里的調度方式基本跟deadline類似。cfq_queue會對進入隊列的每一個請求進行兩次入隊,一個放進fifo中,另一個放進按訪問扇區順序作為key的紅黑樹中。默認從紅黑樹中取請求進行處理,當請求的延時時間達到deadline時,就從紅黑樹中取等待時間最長的進行處理,以保證請求不被餓死。這就是整個cfq的調度流程,當然其中還有很多細枝末節沒有交代,比如合並處理以及順序處理等等。1.2cfq的參數調整理解整個調度流程有助於我們決策如何調整cfq的相關參數。所有cfq的可調參數都可以在/sys/class/block/sda/queue/iosched/目錄下找到,當然,在你的系統上,請將sda替換為相應的磁碟名稱。我們來看一下都有什麼:這些參數部分是跟機械硬碟磁頭尋道方式有關的,如果其說明你看不懂,請先補充相關知識:back_seek_max:磁頭可以向後定址的最大范圍,默認值為16M。back_seek_penalty:向後定址的懲罰系數。這個值是跟向前定址進行比較的。以上兩個是為了防止磁頭尋道發生抖動而導致定址過慢而設置的。基本思路是這樣,一個io請求到來的時候,cfq會根據其定址位置預估一下其磁頭尋道成本。設置一個最大值back_seek_max,對於請求所訪問的扇區號在磁頭後方的請求,只要定址范圍沒有超過這個值,cfq會像向前定址的請求一樣處理它。再設置一個評估成本的系數back_seek_penalty,相對於磁頭向前定址,向後定址的距離為1/2(1/back_seek_penalty)時,cfq認為這兩個請求定址的代價是相同。這兩個參數實際上是cfq判斷請求合並處理的條件限制,凡事復合這個條件的請求,都會盡量在本次請求處理的時候一起合並處理。fifo_expire_async:設置非同步請求的超時時間。同步請求和非同步請求是區分不同隊列處理的,cfq在調度的時候一般情況都會優先處理同步請求,之後再處理非同步請求,除非非同步請求符合上述合並處理的條件限制范圍內。當本進程的隊列被調度時,cfq會優先檢查是否有非同步請求超時,就是超過fifo_expire_async參數的限制。如果有,則優先發送一個超時的請求,其餘請求仍然按照優先順序以及扇區編號大小來處理。fifo_expire_sync:這個參數跟上面的類似,區別是用來設置同步請求的超時時間。slice_idle:參數設置了一個等待時間。這讓cfq在切換cfq_queue或servicetree的時候等待一段時間,目的是提高機械硬碟的吞吐量。一般情況下,來自同一個cfq_queue或者servicetree的IO請求的定址局部性更好,所以這樣可以減少磁碟的定址次數。這個值在機械硬碟上默認為非零。當然在固態硬碟或者硬RAID設備上設置這個值為非零會降低存儲的效率,因為固態硬碟沒有磁頭定址這個概念,所以在這樣的設備上應該設置為0,關閉此功能。group_idle:這個參數也跟上一個參數類似,區別是當cfq要切換cfq_group的時候會等待一段時間。在cgroup的場景下,如果我們沿用slice_idle的方式,那麼空轉等待可能會在cgroup組內每個進程的cfq_queue切換時發生。這樣會如果這個進程一直有請求要處理的話,那麼直到這個cgroup的配額被耗盡,同組中的其它進程也可能無法被調度到。這樣會導致同組中的其它進程餓死而產生IO性能瓶頸。在這種情況下,我們可以將slice_idle=0而group_idle=8。這樣空轉等待就是以cgroup為單位進行的,而不是以cfq_queue的進程為單位進行,以防止上述問題產生。low_latency:這個是用來開啟或關閉cfq的低延時(lowlatency)模式的開關。當這個開關打開時,cfq將會根據target_latency的參數設置來對每一個進程的分片時間(slicetime)進行重新計算。這將有利於對吞吐量的公平(默認是對時間片分配的公平)。關閉這個參數(設置為0)將忽略target_latency的值。這將使系統中的進程完全按照時間片方式進行IO資源分配。這個開關默認是打開的。我們已經知道cfq設計上有「空轉」(idling)這個概念,目的是為了可以讓連續的讀寫操作盡可能多的合並處理,減少磁頭的定址操作以便增大吞吐量。如果有進程總是很快的進行順序讀寫,那麼它將因為cfq的空轉等待命中率很高而導致其它需要處理IO的進程響應速度下降,如果另一個需要調度的進程不會發出大量順序IO行為的話,系統中不同進程IO吞吐量的表現就會很不均衡。就比如,系統內存的cache中有很多臟頁要寫回時,桌面又要打開一個瀏覽器進行操作,這時臟頁寫回的後台行為就很可能會大量命中空轉時間,而導致瀏覽器的小量IO一直等待,讓用戶感覺瀏覽器運行響應速度變慢。這個low_latency主要是對這種情況進行優化的選項,當其打開時,系統會根據target_latency的配置對因為命中空轉而大量佔用IO吞吐量的進程進行限制,以達到不同進程IO佔用的吞吐量的相對均衡。這個開關比較合適在類似桌面應用的場景下打開。target_latency:當low_latency的值為開啟狀態時,cfq將根據這個值重新計算每個進程分配的IO時間片長度。quantum:這個參數用來設置每次從cfq_queue中處理多少個IO請求。在一個隊列處理事件周期中,超過這個數字的IO請求將不會被處理。這個參數只對同步的請求有效。slice_sync:當一個cfq_queue隊列被調度處理時,它可以被分配的處理總時間是通過這個值來作為一個計算參數指定的。公式為:time_slice=slice_sync+(slice_sync/5*(4-prio))。這個參數對同步請求有效。slice_async:這個值跟上一個類似,區別是對非同步請求有效。slice_async_rq:這個參數用來限制在一個slice的時間范圍內,一個隊列最多可以處理的非同步請求個數。請求被處理的最大個數還跟相關進程被設置的io優先順序有關。1.3cfq的IOPS模式我們已經知道,默認情況下cfq是以時間片方式支持的帶優先順序的調度來保證IO資源佔用的公平。高優先順序的進程將得到的時間片長度,而低優先順序的進程時間片相對較小。當我們的存儲是一個高速並且支持NCQ(原生指令隊列)的設備的時候,我們最好可以讓其可以從多個cfq隊列中處理多路的請求,以便提升NCQ的利用率。此時使用時間片的分配方式分配資源就顯得不合時宜了,因為基於時間片的分配,同一時刻最多能處理的請求隊列只有一個。這時,我們需要切換cfq的模式為IOPS模式。切換方式很簡單,就是將slice_idle=0即可。內核會自動檢測你的存儲設備是否支持NCQ,如果支持的話cfq會自動切換為IOPS模式。另外,在默認的基於優先順序的時間片方式下,我們可以使用ionice命令來調整進程的IO優先順序。進程默認分配的IO優先順序是根據進程的nice值計算而來的,計算方法可以在manionice中看到,這里不再廢話。2、deadline:最終期限調度deadline調度演算法相對cfq要簡單很多。其設計目標是:在保證請求按照設備扇區的順序進行訪問的同時,兼顧其它請求不被餓死,要在一個最終期限前被調度到。我們知道磁頭對磁碟的尋道是可以進行順序訪問和隨機訪問的,因為尋道延時時間的關系,順序訪問時IO的吞吐量更大,隨機訪問的吞吐量小。如果我們想為一個機械硬碟進行吞吐量優化的話,那麼就可以讓調度器按照盡量復合順序訪問的IO請求進行排序,之後請求以這樣的順序發送給硬碟,就可以使IO的吞吐量更大。但是這樣做也有另一個問題,就是如果此時出現了一個請求,它要訪問的磁軌離目前磁頭所在磁軌很遠,應用的請求又大量集中在目前磁軌附近。導致大量請求一直會被合並和插隊處理,而那個要訪問比較遠磁軌的請求將因為一直不能被調度而餓死。deadline就是這樣一種調度器,能在保證IO最大吞吐量的情況下,盡量使遠端請求在一個期限內被調度而不被餓死的調度器。

3. java 中 阻塞隊列 非阻塞隊列 和普通隊列的區別是什麼

阻塞隊列與普通隊列的區別在於,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列里添加元素的操作會被阻塞。試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。同樣,試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程使隊列重新變得空閑起來,如從隊列中移除一個或者多個元素,或者完全清空隊列.
1.ArrayDeque, (數組雙端隊列)
2.PriorityQueue, (優先順序隊列)
3.ConcurrentLinkedQueue, (基於鏈表的並發隊列)
4.DelayQueue, (延期阻塞隊列)(阻塞隊列實現了BlockingQueue介面)
5.ArrayBlockingQueue, (基於數組的並發阻塞隊列)
6.LinkedBlockingQueue, (基於鏈表的FIFO阻塞隊列)
7.LinkedBlockingDeque, (基於鏈表的FIFO雙端阻塞隊列)
8.PriorityBlockingQueue, (帶優先順序的無界阻塞隊列)
9.SynchronousQueue (並發同步阻塞隊列)
阻塞隊列和生產者-消費者模式
阻塞隊列(Blocking queue)提供了可阻塞的put和take方法,它們與可定時的offer和poll是等價的。如果Queue已經滿了,put方法會被阻塞直到有空間可用;如果Queue是空的,那麼take方法會被阻塞,直到有元素可用。Queue的長度可以有限,也可以無限;無限的Queue永遠不會充滿,所以它的put方法永遠不會阻塞。
阻塞隊列支持生產者-消費者設計模式。一個生產者-消費者設計分離了「生產產品」和「消費產品」。該模式不會發現一個工作便立即處理,而是把工作置於一個任務(「to do」)清單中,以備後期處理。生產者-消費者模式簡化了開發,因為它解除了生產者和消費者之間相互依賴的代碼。生產者和消費者以不同的或者變化的速度生產和消費數據,生產者-消費者模式將這些活動解耦,因而簡化了工作負荷的管理。
生產者-消費者設計是圍繞阻塞隊列展開的,生產者把數據放入隊列,並使數據可用,當消費者為適當的行為做准備時會從隊列中獲取數據。生產者不需要知道消費者的省份或者數量,甚至根本沒有消費者—它們只負責把數據放入隊列。類似地,消費者也不需要知道生產者是誰,以及是誰給它們安排的工作。BlockingQueue可以使用任意數量的生產者和消費者,從而簡化了生產者-消費者設計的實現。最常見的生產者-消費者設計是將線程池與工作隊列相結合。
阻塞隊列簡化了消費者的編碼,因為take會保持阻塞直到可用數據出現。如果生產者不能足夠快地產生工作,讓消費者忙碌起來,那麼消費者只能一直等待,直到有工作可做。同時,put方法的阻塞特性也大大地簡化了生產者的編碼;如果使用一個有界隊列,那麼當隊列充滿的時候,生產者就會阻塞,暫不能生成更多的工作,從而給消費者時間來趕進進度。
有界隊列是強大的資源管理工具,用來建立可靠的應用程序:它們遏制那些可以產生過多工作量、具有威脅的活動,從而讓你的程序在面對超負荷工作時更加健壯。
雖然生產者-消費者模式可以把生產者和消費者的代碼相互解耦合,但是它們的行為還是間接地通過共享隊列耦合在一起了
類庫中包含一些BlockingQueue的實現,其中LinkedBlockingQueue和ArrayBlockingQueue是FIFO隊列,與 LinkedList和ArrayList相似,但是卻擁有比同步List更好的並發性能。PriorityBlockingQueue是一個按優先順序順序排序的隊列,當你不希望按照FIFO的屬性處理元素時,這個PriorityBolckingQueue是非常有用的。正如其他排序的容器一樣,PriorityBlockingQueue可以比較元素本身的自然順序(如果它們實現了Comparable),也可以使用一個 Comparator進行排序。
最後一個BlockingQueue的實現是SynchronousQueue,它根本上不是一個真正的隊列,因為它不會為隊列元素維護任何存儲空間。不過,它維護一個排隊的線程清單,這些線程等待把元素加入(enqueue)隊列或者移出(dequeue)隊列。因為SynchronousQueue沒有存儲能力,所以除非另一個線程已經准備好參與移交工作,否則put和take會一直阻止。SynchronousQueue這類隊列只有在消費者充足的時候比較合適,它們總能為下一個任務作好准備。
非阻塞演算法
基於鎖的演算法會帶來一些活躍度失敗的風險。如果線程在持有鎖的時候因為阻塞I/O,頁面錯誤,或其他原因發生延遲,很可能所有的線程都不能前進了。
一個線程的失敗或掛起不應該影響其他線程的失敗或掛起,這樣的演算法成為非阻塞(nonblocking)演算法;如果演算法的每一個步驟中都有一些線程能夠繼續執行,那麼這樣的演算法稱為鎖自由(lock-free)演算法。在線程間使用CAS進行協調,這樣的演算法如果能構建正確的話,它既是非阻塞的,又是鎖自由的。非競爭的CAS總是能夠成功,如果多個線程以一個CAS競爭,總會有一個勝出並前進。非阻塞演算法堆死鎖和優先順序倒置有「免疫性」(但它們可能會出現飢餓和活鎖,因為它們允許重進入)。
非阻塞演算法通過使用低層次的並發原語,比如比較交換,取代了鎖。原子變數類向用戶提供了這些底層級原語,也能夠當做「更佳的volatile變數」使用,同時提供了整數類和對象引用的原子化更新操作

4. Laravel 如何同時使用不同資料庫驅動

queue配置
首先說明一下我之前的項目中如何使用queue的。
我們現在的項目都是用的symfony,老一點的項目用的symfony1poser/vendor/bin 加入到環境變數中。
source ~/.bash_profile 就可以直接在命令行中使用laravel了。試一下。
laravel -V 能夠看到下面的,就代表成功了。
Laravel Installer version 1.2.1 2、創建項目。
laravel new guagua 3、配置redis和queue。
4、創建controller,
php artisan make:controller DefaultController 在controller的action中push100個queue的任務。
for($i = 0; $i < 100; $i ++) { Queue::push(new SendEmail("ssss".$i)); } 5、創建queue的Command
php artisan make:command SendEmail --queued 修改app/Commands/SendEmail.php,添加一個私有變數。
protected $msg; 同時修改構造函數。
public function __construct($msg) { $this->msg = $msg; } 再修改的handle方法
public function handle() { sleep(4); echo $this->msg."\t".date("Y-m-d H:i:s")."\n"; $this->delete(); } 6、修改routes
Route::get('/', [ 'as' => 'index', 'uses' => 'DefaultController@index' ]); 7、監聽queue
php artisan queue:listen 為了驗證多任務處理,我們同時開三個窗口運行同樣的命令。
8、用laravel內建的server啟動服務
php artisan serve --port 8080 打開瀏覽器,訪問localhost:8080/頁面。當然也可以用nginx,apache之類的。但是需要各種配置,還是內建的使用方便。
在控制台就能看到各個queue執行的情況了,如下圖。可以看到100個任務被三個work平分了。

到此,基本達到了我想要的效果。驗證了laravel可以簡單實現queue,並且可以多任務處理。
make command生成的代碼中use App\Commands\Command ,但是運行時提示沒有這個文件。 解決辦法,修改為 use Illuminate\Console\Command; 不知道為什麼會出現這個低級問題,難道是我mac系統問題,還是我的人品問題。
在controller的action中push隊列的時候,沒有非同步執行,還是在action的腳本中執行的。 發現是配置問題,原來不僅僅要修改config中的queue.php,還要修改.evn中相關配置。 雖然問題解決了,但是還是覺得蛋疼,不能理解。還需要在學習學習laravel。
非同步隊列使用方法

1.配置
關於隊列的定義,這里就不作介紹了。我們要使用非同步隊列就有兩個關鍵:
(1)存儲隊列的地方
(2)執行任務的服務
打開 config/queue.php ,這是Laravel5關於隊列的配置文件。首先我們可以通過 default 參數指定默認隊列驅動,默認配置是 sync , 這是同步隊列,我們要做非同步隊列首先就要改變這里。假設我們用 database 作為驅動,隊列任務將會存放在資料庫中,而我們後面會另外啟動一個後台服務來處理隊列任務,這就是非同步方式了。
'default' => 'database' 修改完配置後,我們需要創建一個表來存放隊列任務,Laravel5已經在自帶artisan命令中內置了一個指令用來生成數據遷移,只需要兩條命令即可,當然你得實現配置好資料庫連接。
php artisan queue:table php artisan migrate 這樣就自動在資料庫中創建了 jobs 表。
2.啟動隊列監聽服務
通過下面這條指令啟動隊列監聽服務,它會自動處理 jobs 表中的隊列任務:
php artisan queue:listen 在linux中,如果想讓它在後台執行,可以這樣:
nohup php artisan queue:listen & 3.添加隊列任務
關於隊列任務的添加,手冊里說的比較詳細,這里就簡單舉個例子吧。
首先,通過artisan創建一個隊列命令:
php artisan make:command SendEmail --queued 這樣會生成 app/Commands/SendEmail.php 這個類文件,這個類會被標識為隊列命令,你可以在 handle 方法中寫自己的業務邏輯。
在控制器中,可以簡單通過 Bus::dispatch 分發任務:
Bus::dispatch(new \App\Commands\SendEmail()); 你會發現任務不會立即執行,而是被放到 jobs 表中,由隊列監聽服務處理。
更詳細的用法建議參考 command bus 和 queue 相關的手冊章節。

5. 為什麼rabbit mq 本地伺服器不能系統根據配置文件自動創建隊列

一、WebSphere MQ命令行命令 1、停止隊列管理器 endmqm [-z] [([-c -w -i -p] [-r] [-s]) -x] QMgrName endmqm mqm_name 使用默認選項停止隊列管理器需要等待當前的應用連接完成並斷開。 -i 立即停止隊列管理器。 -w 需要等待所有的應用停止以後才會真正關閉隊列管理器 -p 使用以上參數都無法正常停止隊列管理器的情況下可以使用該參數 2、啟動隊列管理器 strmqm [-z] [-a -c -r -x] [-d noneminimalall] [-f] [-ns] QMgrName 隊列管理器必須在完全停止時才能被啟動。 3、創建隊列管理器 crtmqm [-z] [-q] [-c Text] [-d DefXmitQ] [-h MaxHandles] [-md DataPath] [-g ApplicationGroup] [-t TrigInt] [-u DeadQ] [-x MaxUMsgs] [-lp LogPri] [-ls LogSec] [-lc -ll] [-lf LogFileSize] [-ld LogPath] QMgrName 隊列管理器名大小寫敏感且不支持空字元串,長度為48位元組,同一網路中不能有重名。 創建隊列管理器的時侯最好創建死信隊列用於存放無法發送的信息,保證通道不會因為無法發送信息而被關閉。 crtmqm -u deadq_name mqm_name 4、刪除隊列管理器 dltmqm [-z] QMgrName 刪除隊列管理器會完全刪除其所擁有的對象和相關信息,並且是不可恢復的。要刪除一個隊列管理器首先要保證他是停止的。 二、WebSphere MQ Script. (MQSC) commands WebSphere MQ Script. (MQSC) commands是常常用來管理隊列管理器對象的。這些對象包括隊列管理器本身,隊列,名稱列表,通道,客戶端通道,監聽,服務等。使用runmqsc 隊列管理名來啟動,可以運行單個的命令,也可以通過命令集的腳本來運行。 本地隊列管理器的作用是接收遠程或本地的信息流,並將本地隊列中的信息流取出以供應用程序使用。在做這些工作之前需要定義相關的隊列管理器,隊列和通道等,而這些工作是由WebSphere MQ Script. (MQSC) commands來完整的。在Windows及Linux環境下也可以通過WebSphere MQ Explorer來完成。 啟動WebSphere MQ Script. (MQSC) :runmqsc [-e] [-v] [-w WaitTime [-x] [-m LocalQMgrName]] [QMgrName] runmqsc mqm_name。通過命令runmqsc啟動隊列管理器的命令伺服器。WebSphere MQ Explorer也能完成相同的任務。runmqsc的相關命令有三種運行方式,Verify a command without running it,Run a command on a local queue manager,Run a command on a remote queue manager。runmqsc的相關命令在解釋的時候都會以大寫來解釋,比如DEFINE,ALTER,RESET等。但是這些命令並不是大小寫敏感的。每行runmqsc命令最長只能到8個字元,可以通過-或者+連接下一行,-是從下一行的第一個字元開始,+是從下一行的第一個非空字元開始。而且所有命令與平台無關。runmqsc的標准輸入是鍵盤,標准輸出時屏幕,我們可以通過<,>重定向。例如從腳本輸入命令runmqsc </path/filename.in,將結果重定向到文件runmqsc>/path/filename.out。 1、顯示隊列管理器屬性(DISPLAY QMGR): DISPLAY QMGR顯示當前隊列管理器的所有屬性,也可以使用DISPLAY QMGR 屬性名,單獨查看當前隊列管理器的某個特定屬性。 2、 更改隊列管理器屬性(ALTER QMGR ): ALTER QMGR 用於更改隊列管理器的相關屬性,例如 ALTER QMGR MAXHANDS(255),這個命令將默認隊列管理器的MAXHANDS由256更改為255。 3、創建本地隊列(DEFINE QLOCAL ): DEFINE QLOCAL Q_LOCAL_NAME,在創建隊列的時候可以定義相關屬性的值,如果沒有定義則使用默認值。也可以全部使用默認值,最後通過ALTER QLOCAL命令來修改相關屬性。在同一個隊列管理器中不能有同名的隊列,可以使用REPLACE關鍵字重建已有的隊列。 4、修改本地隊列屬性(ALTER QLOCAL): ALTER QLOCAL Q_LOCAL_NAME NEW_ATTRIBUTE。已經定義了的本地隊列可以使用ALTER QLOCAL 命令對其屬性進行修改。 5、顯示本地隊列屬性(DISPLAY QLOCAL): DISPLAY QLOCAL Q_LOCAL_NAME ATTRIBUTE。此命令用於顯示本地隊列的屬性,可以使用默認的顯示全部屬性,也可以顯示指定的屬性。 6、復制本地隊列(DEFINE QLOCAL NEW LIKE OLD): DEFINE QLOCAL NEW LIKE OLD。此命令可以創建一個屬性與OLD完全一樣的本地隊列。當然,我們也可以在語句後面指定屬性的詳細信息,沒有指定的則繼承OLD的對應屬性,指定了的則使用新的屬性。 7、清除本地隊列中的消息(CLEAR QLOCAL): CLEAR QLOCAL Q_LOCAL_QUEUE。此命令用於清除本地隊列中存儲的信息。在清除信息的時候系統不會給出任何提示,而是直接把信息刪除。在一下兩種情況下不能使用CLEAR QLOCAL,本地隊列中存儲的有在最近一次隊列同步以後未提交的信息,有應用程序打開使用本地此隊列。 8、刪除本地隊列(DELETE QLOCAL): DELETE QLOCAL Q_LOCAL_QUEUE。此命令用於刪除本地隊列,當本地隊列中存在有沒有提交的數據此隊列不能刪除。如果隊列中存在數據,且數據是提交了的,可以使用PURGE關鍵字刪除本地隊列。例如DELETE QLOCAL (Q_LOCAL_QUEUE) PURGE,在刪除的時候可以指定NOPURGE關鍵字代替PURGE以保護刪除的隊列中可能存在的已提交數據。 三、PCF commands PCF commands允許管理員通過編程的方式將MQ的日常管理任務集成在程序中。包括創建隊列,預定義隊列,更改隊列管理器等, PCF commands與MQSC鎖實現的功能是相同。

6. 如何實現ring buffer

消息驅動機制是 GUI 系統的基礎,消息驅動的底層基礎設施之一是消息隊列,它是整個 GUI
系統運轉中樞,本文介紹了一個基於環形隊列的消息隊列實現方法,給出了它的數據結構、主要操作流程和核心代碼。

環形隊列

環行隊列是一種首尾相連的隊列數據結構,遵循先進先出原則,如下圖所示:

ring buffer
示意圖

在環形隊列中用一組連續地址的存儲單元依次存放從隊列頭到隊列尾的元素,通過兩個指針
read_pos 和 write_pos 分別指向讀取位置和寫入位置。

初始化隊列時,令 read_pos = write_pos = 0,每當寫入一個新元素時,
write_pos 增 1;每當讀取一個元素時,read_pos 增 1 。若隊列已滿,不能往隊列寫入數據;若隊列為空,則不能讀取數據。判斷對列是否為滿的的方法是看 (write_pos + 1)% QUEUE_SIZE == read_pos
是否成立,判斷隊列是否為空的方法是看 write_pos == read_pos 是否成立。

鑒於多個線程同時訪問環形隊列,需要考慮線程之間的互斥和同步問題,擬採用鎖控制多個線程互斥訪問環形隊列,使用信號量控制線程之間的同步。

一段時間內只能有一個線程獲得鎖,當它持有鎖時,其它線程要訪問環形隊列必須等待,直到前者釋放鎖。由此,鎖可以保證多個線程互斥的訪問環形隊列。

線程從隊列對數據前首先判斷信號量是否大於 1
,若是,則從隊列讀數據;否則,進入等待狀態,直到信號量大於 1 為止;線程往隊列寫入一個數據後,會將信號量增 1
,若有線程在等待,則會被喚醒。由此,信號量實現了多線程同步訪問環形隊列。

流程圖

下圖是環形緩沖區的初始化、讀數據、寫數據的主要流程。

ring buffer
流程圖

初始化時為環形隊列分配內存空間,並完成鎖和信號量的初始化;

若往環形隊列寫數據,首先要獲得鎖, 若鎖已被佔用,則進入等待狀態,否
則進一步去判斷環形隊列是否已滿。若滿了,則釋放鎖並返回;若隊列未滿,將 數據寫入 write_pos 位置,write_pos 增 1,釋放鎖並將信號量增
1,表示 已寫入一個數據;

若從環形隊列讀數據,首先判斷信號量是否大於 1 ,若不是,則等待,否則
去獲取鎖,若鎖已被佔用,則等待,否則從 read_pos 位置讀取數據,將 read_pos 增 1 ,釋放鎖,讀取完畢。

數據結構

環形隊列的數據結構如下所示:
typedef _MSG {
int message;
void* param;
} MSG;

typedef _MSGQUE {
pthread_mutex_t lock;
sem_t wait;

MSG* msg;
int size;

int read_ops;
int write_ops;
} MSGQUEUE;

環形隊列包括如下數據:

lock:互斥鎖;

wait:信號量

msg:指向數據區的指針;

size:環形隊列數據最大個數;

read_ops:讀取位置;

write_ops:寫入位置。

隊列初始化

初始化主要完成三個任務:

為環形隊列分配內存;

初始化互斥鎖,用 pthread_mutex_init
完成;

初始化信號量,用 sem_init
完成。
/* Create message queue */
_msg_queue = malloc (sizeof (MSGQUEUE));

/* init lock and sem */
pthread_mutex_init (&_msg_queue->lock, NULL);
sem_init (&_msg_queue->wait, 0, 0);

/* allocate message memory */
_msg_queue -> msg = malloc (sizeof(MSG) * nr_msg);
_msg_queue -> size = nr_msg;

寫操作

如上面的流程圖介紹,寫操作主要包括如下幾步: - 獲取鎖;

判斷隊列是否已滿;

若沒滿,將數據寫入 write_pos 處,將 write_pos 增 1,並判斷
write_pos 是否越界;

釋放鎖,並將信號量增 1。
/* lock the message queue */
pthread_mutex_lock (_msg_queue->lock);

/* check if the queue is full. */
if ((_msg_queue->write_pos + 1)% _msg_queue->size == _msg_queue->read_pos) {
/* Message queue is full. */
pthread_mutex_unlock (_msg_queue->lock);
return;
}

/* write a data to write_pos. */
_msg_queue -> msg [write_pos] = *msg;
write_pos ++;

/* check if write_pos if overflow. */
if (_msg_queue->write_pos >= _msg_queue->size)
_msg_queue->write_pos = 0;

/* release lock */
pthread_mutex_unlock (_msg_queue->lock);

sem_post (_msg_queue->wait);

讀操作

同理,讀操作分如下幾個步驟:

檢查信號量;

獲取鎖;

判斷隊列是否為空;

若不為空,則讀取 read_ops 處的數據,將 read_ops 增 1,並判斷
read_pos 是否越界;

並釋放鎖。
sem_wait (_msg_queue->wait);

/* lock the message queue */
pthread_mutex_lock (_msg_queue->lock);

/* check if queue is empty */
if (_msg_queue->read_pos != _msg_queue->write_pos) {
msg = _msg_queue->msg + _msg_queue->read_pos;

/* read a data and check if read_pos is overflow */
_msg_queue->read_pos ++;
if (_msg_queue->read_pos >= _msg_queue->size)
_msg_queue->read_pos = 0;

return;
}

/* release lock*/
pthread_mutex_unlock (_msg_queue->lock);

7. 高分求助SQL存儲過程詳細分析,最好每句解釋一下

ALTER procere [dbo].[wvsp_updateTaskStatus]
@ttype int,
@id int,
@status int // 這一段主要是定義存儲過程需要的參數
as
begin 開始存儲過程
if @ttype=0 // 開始判斷 如果傳進來的@ttype=0
begin //開始執行
if (@status=3) //如果(@status=3)
begin //開始執行函數
insert into tb_queuelog([type],mid,code,otherparams,created) //往表名為tb_queuelog的表插入數據,以上為要插入的欄位
select @ttype,mid,code,otherparams,created
from tb_mergequeue
where id=@id //要插入欄位的數據從表tb_mergequeue 搜索出來,與上表要插入的欄位一一對應,搜索條件為:id=@id

delete from tb_mergequeue
where id=@id //刪除tb_mergequeue表中where id=@id的欄位
end //結束@status=3條件循環
else//如果(@status不等於3,進入另一個方法
update tb_mergequeue
set status=@status
where id=@id //更新tb_mergequeue的數據

end //結束(@status不等於3的條件循環
else if(@ttype=1) //如果 (@ttype=1 -- 頁面同步任務
begin開始
if (@status=3)
begin
insert into tb_queuelog([type],mid,code,otherparams,created)
select @ttype,mid,code,otherparams,created
from tb_syncqueue
where id=@id

delete from tb_syncqueue
where id=@id
end
else
update tb_syncqueue
set status=@status
where id=@id
end

select @@rowcount //取得記錄總數
end 結束存儲過程

8. 隊列中存取數據元素的原則是A.先進先出 B.後進先出 C.先進後出 D.隨意進出

先進先出。

隊列是先進先出的線性表。隊列和棧一樣,在實際程序的演算法設計和計算機一些其他分支里,都有很多重要的應用,比如計算機操作系統對進程或作業的優先順序調度演算法,對離散事件的模擬演算法,還有計算機主機和外部設備運行速度不匹配的問題解決等。

(8)不存儲元素的同步隊列擴展閱讀:

注意事項:

使用順序存儲結構表示隊列時,首先申請足夠大的內存空間建立一個數組,除此之外,為了滿足隊列從隊尾存入數據元素,從隊頭刪除數據元素,還需要定義兩個指針分別作為頭指針和尾指針。

當有數據元素進入隊列時,將數據元素存放到隊尾指針指向的位置,然後隊尾指針增加 1。當刪除對頭元素(即使想刪除的是隊列中的元素,也必須從隊頭開始一個個的刪除)時,只需要移動頭指針的位置就可以。

9. laravel自帶隊列和redis實現隊列哪個效率高

Laravel 的隊列只是一些用於操控隊列的代碼,跟後端具體的數據存儲方式無關

Redis 就是數據存儲的一種方式,還有 MySQL 等
默認的 Sync 就是同步隊列其實並不是隊列,它的作用就是把要在隊列中執行的代碼放到當前 PHP 線程中阻塞執行,因為 PHP 默認是單線程阻塞執行的,不執行完,就無法給用戶瀏覽器返回結果

10. 到底什麼是消息隊列Java中如何實現消息隊列

「消息隊列」是在消息的傳輸過程中保存消息的容器。和我們學過的LinkedHashMap,TreeSet等一樣,都是容器。既然是容器,就有有自己的特性,就像LinkedHashMap是以鍵值對存儲。存取順序不變。而消息隊列,看到隊列就可以知道。這個容器裡面的消息是站好隊的,一般遵從先進先出原則。

java中已經為我們封裝好了很多的消息隊列。在java 1.5版本時推出的java.util.concurrent中有很多現成的隊列供我們使用。特性繁多,種類齊全。是你居家旅遊開發必備QAQ。

下面簡單列舉這個包中的消息隊列

  1. :阻塞隊列 BlockingQueue

  2. 數組阻塞隊列 ArrayBlockingQueue

  3. 延遲隊列 DelayQueue

  4. 鏈阻塞隊列 LinkedBlockingQueue

  5. 具有優先順序的阻塞隊列 PriorityBlockingQueue

  6. 同步隊列 SynchronousQueue

  7. 阻塞雙端隊列 BlockingDeque

  8. 鏈阻塞雙端隊列 LinkedBlockingDeque

    不同的隊列不同的特性決定了隊列使用的時機,感興趣的話你可以詳細了解。具體的使用方式我就不贅述了