簡體   English   中英

鎖定免費隊列 - 單一生產者,多個消費者

[英]Lock Free Queue — Single Producer, Multiple Consumers

我正在尋找一種方法來實現支持單個生產者和多個消費者的無鎖隊列數據結構。 我看過Maged Michael和Michael Scott(1996)的經典方法,但他們的版本使用鏈表。 我想要一個使用有界循環緩沖區的實現。 什么東西使用原子變量?

另外,我不確定為什么這些經典方法是為需要大量動態內存管理的鏈表設計的。 在多線程程序中,所有內存管理例程都是序列化的。 我們不是通過將它們與動態數據結構結合使用來破壞無鎖方法的好處嗎?

我試圖在英特爾64位架構上使用pthread庫在C / C ++中編寫代碼。

謝謝Shirish

使用圓形緩沖器需要鎖定,因為需要阻塞以防止頭部越過尾部。 但是否則頭部和尾部指針可以很容易地以原子方式更新。 或者在某些情況下,緩沖區可能非常大,以至於覆蓋不是問題。 (在現實生活中,你會在自動交易系統中看到這一點,循環緩沖區的大小可以容納X分鍾的市場數據。如果你落后X分鍾,你會比覆蓋你的緩沖區更糟糕的問題)。

當我在C ++中實現MS隊列時,我使用堆棧構建了一個無鎖分配器,這很容易實現。 如果我有MSQueue,那么在編譯時我知道sizeof(MSQueue :: node)。 然后我制作一堆所需大小的N個緩沖區。 N可以增長,即如果pop()返回null,很容易向堆請求更多的塊,並將它們推送到堆棧中。 在可能阻塞的更多內存調用之外,這是一個無鎖操作。

請注意,T不能有非平凡的dtor。 我研究過一個允許非平凡dtors的版本,實際上是有效的。 但我發現將T指向我想要的T,生產者釋放所有權,消費者獲得所有權更容易。 這當然要求T本身是使用lockfree方法分配的,但我在堆棧中使用的相同分配器也在這里工作。

在任何情況下,無鎖編程的要點都不是數據結構本身更慢。 要點是這樣的:

  1. lock free使我獨立於調度程序。 基於鎖的編程取決於調度程序,以確保鎖的持有者正在運行,以便他們可以釋放鎖。 這就是導致“優先級倒置”的原因在Linux上有一些鎖定屬性可以確保這種情況發生
  2. 如果我獨立於調度程序,操作系統管理時間片的時間要容易得多,而且我的上下文切換要少得多
  3. 使用lockfree方法編寫正確的多線程程序更容易,因為我不必擔心死鎖,活鎖,調度,同步等等。這對於共享內存實現尤其如此,其中進程可能會在共享內存中鎖定時死亡,並且沒有辦法釋放鎖
  4. 無鎖方法更容易擴展。 實際上,我已經使用網絡上的消息傳遞實現了無鎖方法。 像這樣的分布式鎖是一場噩夢

也就是說,在許多情況下,基於鎖定的方法是優選的和/或需要的

  1. 更新昂貴或無法復制的東西時。 大多數無鎖方法使用某種版本控制,即制作對象的副本,更新它,並檢查共享版本是否仍然與復制時相同,然后使當前版本更新版本。 Els再次復制它,應用更新,然后再次檢查。 繼續這樣做直到它工作。 當對象很小但是它們很大,或包含文件句柄等時,這很好,不推薦使用
  2. 大多數類型都無法以無鎖方式訪問,例如任何STL容器。 它們具有需要非原子訪問的不變量,例如assert(vector.size()== vector.end() - vector.begin())。 因此,如果要更新/讀取共享的向量,則必須將其鎖定。

這是一個老問題,但沒有人提供一個公認的解決方案。 所以我為其他可能正在搜索的人提供此信息。

本網站: http//www.1024cores.net

提供一些非常有用的lockfree / waitfree數據結構,並提供詳盡的解釋。

您正在尋求的是讀/寫問題的無鎖解決方案。

請參閱: http//www.1024cores.net/home/lock-free-algorithms/reader-writer-problem

對於傳統的單塊循環緩沖區,我認為這不能通過原子操作安全地完成。 你需要在一次閱讀中做這么多。 假設你有一個具有以下結構的結構:

uint8_t* buf;
unsigned int size; // Actual max. buffer size
unsigned int length; // Actual stored data length (suppose in write prohibited from being > size)
unsigned int offset; // Start of current stored data

在閱讀時你需要做以下(這是我實現它的方式,你可以交換一些步驟,比如我之后討論):

  1. 檢查讀取長度是否超過存儲長度
  2. 檢查偏移量+讀取長度是否超過緩沖區邊界
  3. 讀出數據
  4. 增加偏移量,減少長度

你應該做什么同步(如此原子)才能使這項工作? 實際上在一個原子步驟中組合步驟1和4,或澄清:執行此操作同步:

  1. 檢查read_length,這可能像read_length=min(read_length,length);
  2. 使用read_length減小長度: length-=read_length
  3. 從offset unsigned int local_offset = offset獲取本地副本
  4. 使用read_length增加偏移量: offset+=read_length

之后你可以從local_offset開始做一個memcpy(或其他),檢查你的讀取是否超過循環緩沖區大小(分成2個memcpy),.... 這是“非常”線程安全的,你的寫入方法仍然可以寫入你正在讀取的內存,因此請確保你的緩沖區足夠大,以盡量減少這種可能性。

現在,雖然我可以想象你可以將3和4結合起來(我猜這是他們在鏈表實例中所做的),甚至是原子操作中的1和2,我看不到你在一個原子操作中完成這個整個交易:)。

但是,如果您的消費者非常聰明並且總是知道要閱讀什么,您可以嘗試放棄“長度”檢查。 你還需要一個新的woffset變量,因為用於確定寫入偏移量的(offset + length)%size的舊方法將不再起作用。 請注意,這與鏈表的情況很接近,實際上您總是從列表中讀取一個元素(=固定的,已知大小)。 此外,如果您將其設為循環鏈接列表,您可以閱讀更多內容或寫入您當時正在閱讀的位置!

最后:我的建議,只是使用鎖,我使用CircularBuffer類,對讀取和寫入完全安全)對於實時720p60視頻流,我沒有鎖定速度問題。

這是一個古老的問題,但沒有人提供准確答案的答案。 鑒於(幾乎)相同問題的搜索結果仍然很高,應該有答案,因為存在一個答案。

可能有多個解決方案,但這里有一個實現: https//github.com/tudinfse/FFQ自述文件中引用的會議文件詳述了算法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM