2017-09-22 62 views
1

滿足無鎖進程保證的編寫算法或數據結構的困難之一是動態內存分配:調用類似mallocnew的調用不保證以便攜方式無鎖。然而,存在許多無鎖實現mallocnew,並且還有各種可用於實現無鎖算法/數據結構的無鎖內存分配器。動態鎖定內存分配器

但是,我仍然不明白如何實際上完全滿足無鎖進度保證,除非你特別限制你的數據結構或算法到一些預先分配的靜態內存池。但是,如果你需要動態內存分配,我不明白如何任何指稱的無鎖內存分配器永遠真正無鎖的長期。問題是無論您的無鎖mallocnew可能有多驚人,最終可能會導致內存不足,此時您必須要求操作系統提供更多內存。這意味着最終你必須調用brk()mmap()或者一些這樣的低級別等價物,以實際獲得更多的內存。並且不能保證任何這些低級調用都是以無鎖方式實現的。 (除非您使用的是像MS-DOS這樣的不提供內存保護的古老操作系統,或者您自己編寫了完全無鎖的操作系統 - 兩種不實用的方案或者可能)。那麼,怎樣才能使任何動態內存分配器真正無鎖?

+1

你說的沒錯,但是......由於分配只能從OS請求大塊,並且不需要及時發佈它們,對「sbrk()」或任何其他語言的調用總數可以嚴格限制到無關緊要的程度。當然,這並不能滿足硬實時要求,但我認爲即使動態分配完全無鎖,也不會滿足這些要求。 –

+0

您是否擔心延遲,或者您是否擔心使用https://en.wikipedia.org/wiki/Non-blocking_algorithm?如果是後者,我認爲最好的操作系統使得線程無法在內核中睡眠,同時持有一個會阻止其他線程使用mmap的鎖。所以就編寫一個非阻塞算法而言,我認爲使用'mmap'並不是一個問題,因爲如果一個線程可以在保持一個線程的同時睡眠,那麼鎖只是一個問題。臨界區內的一對CPU高速緩存未命中可能是你得到的最差的。 –

+0

在真實系統上運行的所有分配器在某個時刻不得不耗盡內存;在分配靜態池時運行的無鎖分配器在操作系統認爲你已經擁有足夠的時間的情況下與正常的分配器沒什麼質的區別。 –

回答

3

正如你發現自己一樣,基本的操作系統分配器很可能不是無鎖的,因爲它必須處理多個進程和各種有趣的東西,這使得很難不引入某種鎖。

但是,對於某些情況,「無鎖內存分配」並不意味着「永不鎖定」,而是「統計鎖定非常少以至於無關緊要」。對於除最嚴格的實時系統以外的其他任何應用程序都適用。如果你的鎖沒有高度爭用,那麼鎖或無鎖並不重要 - 鎖的目的不在於它本身的開銷,而在於它容易成爲瓶頸系統中的每個線程或進程都必須通過這個地方來做任何有用的事情,並且因爲它這樣做,所以它必須在隊列中等待[它可能不是真正的隊列,它可能是「誰先喚醒」或者一些其他機制,決定誰在下一個,當前呼叫者之後]。

有幾個不同的選項來解決這個問題:

  • 如果你有一個有限大小的存儲器池,你可以問OS爲所有存儲一次,正在啓動軟件時。內存從操作系統分割出來之後,可以用作無鎖池。明顯的缺點是它可以分配多少內存。然後您必須停止分配(全部失敗應用程序,或者失敗該特定操作)。

    當然,在像Linux或Windows這樣的系統中,仍然不能保證在無鎖的情況下內存分配意味着「即時訪問分配的內存」,因爲系統可以並且將分配內存而無需實際的物理內存支持它,並且只有實際使用內存時,物理內存頁纔會被分配給它。這可能涉及到鎖,例如磁盤I/O會將其他頁面分頁到交換分區。

  • 對於這樣嚴格的實時系統來說,單個系統調用可能爭奪鎖的時間「太多」,解決方案當然是使用專用操作系統,一個具有無鎖(或者至少有一個已知的實時行爲是可以接受的 - 它最多可以鎖定X個microsecons [X可以小於1.0])。實時系統通常有一個內存池和固定大小的存儲桶,用於回收舊分配,這可以以無鎖方式完成 - 存儲桶是一個鏈接列表,因此您可以使用原子比較&從列表中插入/刪除交換操作[可能需要重試,所以雖然它在技術上無鎖,但在爭用情況下不是零等待時間]。

  • 可以工作的另一個解決方案是擁有「每個線程池」。如果你在線程之間傳遞數據,這可能會變得複雜一些,但是如果你接受「爲釋放而釋放的內存可能會以不同的線程結束」(這當然會導致出現問題,「現在所有的內存都在一個線程收集和釋放許多其他線程的信息,所有其它線程都用完了內存「)