這篇文章是以前那篇文章iOS管理對象內存的數據結構以及操做算法--SideTables、RefcountMap、weak_table_t的補充和延伸。若是沒有閱讀過前一篇文章請先看那一篇。
上一篇文章中關於SideTables、SideTable和RefcountMap三者關係可能描述的不太清楚。不少朋友表示看起來暈乎乎的。當初我在研究的時候也是蒙圈了好長一段時間。因此特地寫了這篇文章來補充說明一下,同時也有新的知識擴展。
剛開始寫文章,思路不太清晰。若是文章中有什麼錯誤或者問題,歡迎你們指正。
這裏特別感謝大牆66370午夜時分挑燈夜戰提出的寶貴問題。javascript
本文流程
1、解釋分離鎖是什麼。
2、舉例闡述SideTables、SideTable、RefcountMap三者關係。
3、第一篇文章所說的N路併發是什麼意思。
4、SideTables所使用的Hash算法解密。
5、RefcountMap是什麼結構。html
分離鎖並非一種鎖,而是一種對鎖的用法。(下面繼續上一張感人的手繪圖。哈哈我寫的字也出現了。若是比寫字醜,通常不多有人能比我寫的醜)
java
圖1這樣對一整個表加一把鎖,是咱們平時比較常見的。若是我一次寫操做須要操做表中多個單元格的數據,好比第一次操做0、一、2位置的數據,第二次操做0、二、3位置的數據。像這種狀況鎖的粒度就是以整張表爲單位的,才能保證數據的安全。
圖2這樣對錶中的各個元素分別加一把鎖就是咱們說的分離鎖。適用於表中元素相互獨立,你對第一個元素作寫操做的時候不須要影響到其餘元素。
上文中所說的結構就是SideTables這個大的Hash表中每個小單元格(SideTable)都帶有一把鎖。作寫操做的時候(操做對象引用計數)單元格之間相互獨立,互相沒影響。因此下降了鎖的粒度。算法
對比一下圖1和圖2的併發性。安全
這裏注意區分一下併發和並行的區別。數據結構
當有多個線程在操做時,若是系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分紅若干個時間段,再將時間段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀態.這種方式咱們稱之爲併發(Concurrent).
當系統有一個以上CPU時,則線程的操做有可能非併發.當一個CPU執行一個線程時,另外一個CPU能夠執行另外一個線程,兩個線程互不搶佔CPU資源,能夠同時進行,這種方式咱們稱之爲並行(Parallel)併發
能夠看到在單元格之間相互獨立的狀況下圖2的方法效率更高。
看了上面的例子有同窗會有疑問。既然分離鎖能夠實現高併發,那麼爲何不對每個內存對象加一把鎖呢?爲何還會有Hash表還會衝突呢?這個問題我在下面經過一個例子和RefcountMap一塊兒解釋。ide
##2、爲何SideTables會衝突、SideTable又扮演着什麼角色、RefcountMap是用來幹嗎的?
下面我用一個不太恰當的例子來講明問題
假設有80個學生須要我們安排住宿,同時還要保證學生們的財產安全。應該怎麼安排?
顯然不會給80個學生分別安排80間宿舍,而後給每一個宿舍的大門上加一把鎖。那樣太浪費資源了鎖也挺貴的,太多的宿舍維護起來也很費力氣。
咱們通常的作法是把80個學生分配到10間宿舍裏,每一個宿舍住8我的。假設宿舍號分別是10一、102 、... 110。而後再給他們分配牀位,01號牀、02號牀等。而後給每一個宿舍配一把鎖來保護宿舍內同窗的財產安全。爲何不僅給整個宿舍樓上一把鎖,每次有人進出的時候都把整個宿舍樓鎖上?顯然這樣會形成宿舍樓大門口阻塞。
OK假如如今有人要找102號宿舍的2號牀的人聊天。這我的會怎麼作?高併發
SideTables[10202]
找到了102宿舍SideTable
,而後把102的門一鎖lock
,在他訪問102期間再也不容許其餘訪客訪問102了。(這樣只是阻塞了102的8個兄弟的訪問,而不會影響整棟宿舍樓的訪問)table.refcnts.find(02)
你就能夠找到2號牀的兄弟了。unlock
,這樣其餘須要訪問102的人就能夠繼續進來訪問了。SideTables == 宿舍樓
SideTable == 宿舍
RefcountMap裏存放着具體的牀位複製代碼
蘋果之因此須要創造SideTables的Hash衝突是爲了把對象放到宿舍裏管理,把鎖的粒度縮小到一個宿舍SideTable。RefcountMap的工做是在找到宿舍之後幫助你們找到正確的牀位的兄弟。優化
上一篇文章中提到:
由於是使用對象的內存地址當key因此Hash的分部也很平均。假設Hash表有n個元素,則能夠將Hash的衝突減小到n分之一,支持n路的併發寫操做。
咱們在分配宿舍的時候是先給同窗分配宿舍和牀位,而後再給宿舍和牀位編號。因此咱們能夠很平均的給每一個宿舍分配8我的。
那麼若是咱們用對象內存地址當作Hash算法的key,所獲得的散列值可能會出現某些宿舍分配了4我的,某些宿舍分配了12我的的狀況。這樣人員分配就不平均了。若是某一時間段正好這個宿舍的12我的的訪問量都特別大,那麼訪問起來就又會出現阻塞了。而那4人間的宿舍就會閒置,形成了資源的浪費。會不會形成這種資源浪費主要看兩點。
一、在數據量足夠大的狀況下咱們的key值分部是平均的。由於key值是內存地址。從低位0x0000...0000到高位0xffff...ffff分配。而且操做系統的內存管理模塊自己也會對內存分配作不少優化。畢業年頭長了,內管管理具體的細節我也扯不出來了。趕忙貼一片文章壓壓驚,有興趣的同窗能夠看操做系統內存管理——分區、頁式、段式管理。
二、那麼SideTables使用的Hash算法是什麼呢?咱們來開一個新的大標題。
SideTables的定義:NSObject.mm line 207-209
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}複製代碼
若是看不懂不要緊,當它是一個C++的Map。我們來看StripedMap的定義:objc-private.h line 867-906
其中有用的部分是這樣的
...
//若是是嵌入式系統StripeCount=8。咱們這裏StripeCount=64
enum { StripeCount = 64 };
...
static unsigned int indexForPointer(const void *p) {
//這裏是類型轉換,不用在乎
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
//這裏就是咱們要找的Hash算法了
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}複製代碼
addr
右移4位獲得結果1addr
右移9位獲得結果2由於最後模運算的結果範圍是在0-63之間,可見SideTables一共有64個單元格。
前面文章中提到過if(引用計數器 != table.refcnts.end())
所以有同窗提問end()
是什麼?那麼我們得研究一下RefcountMap是什麼類型的。
看定義NSObject.mm line 137
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;複製代碼
看起來好複雜,先無論它。一路順着定義找下去。找到DenseMap ==> DenseMapBase ==> inline iterator end()
發現我們當前在llvm-DenseMap.h line 77-79。艾瑪嚇死我了怎麼看到llvm了。llvm我也不懂,因此仍是不要扯的太遠。趕忙打開llvm的相關文檔看看DenseMapBase中的公開方法有
固然還有更多其餘方法,我只是截取了一部分。經過這部分咱們能夠看出來咱們操做的refcnts.find()和refcnts.end()其實都是對一個C++迭代器iterator的操做。而end()的狀態表示的是從頭開始查找,一直找到最後都沒有找到。當前指針指向的是結束位,而不是最後一個元素。
因此 if(引用計數器 == table.refcnts.end())
表示查找到最後都沒找到if(引用計數器 != table.refcnts.end())
表示中途找到了。
講到這裏想講的內容已經講完啦,歡迎評論、點贊、轉發。
歡迎加個人微博weibo.com/xuyang186
轉載請註明出處,謝謝。