當前位置:
首頁 > 知識 > php7擴展使用持久化hash

php7擴展使用持久化hash

最近項目需要在PHP7的擴展里,維護一個全局的持久化zend_array,在多次請求之間可以共享使用。

在這裡簡單記錄一下實現和原理。

首先是定義一個全局的 :

在擴展初始化回調里,分配並初始化一個 :

首先 自身的內存一定是 來創建的持久化內存,相當於malloc而不是emalloc,不會在請求結束後被釋放。

之後,調用 初始化這個array,需要注意的是value的dtor回調函數並不是 ,而是我自己實現的 函數。

另外,最後一個參數persistant=1,這樣 在內部分配哈希桶等內存時也會使用pemalloc分配持久化內存。

既然要持久化,除了 本身以外,保存在 里的zval也一定要持久化內存,包括key是持久化的 ,value是持久化的任意類型zval。

這裡就說說,為什麼要自定義value的dtor函數,而不用zend API自帶的 ,這裡截取了它的實現片段:

重點關注最後一個實現函數,當 里的某個value引用計數為0的時候將被調用。對於string類型來說, 的內部實現其實判斷了 是否為持久化內存:

可見 里的gc欄位保存了 標記,這是 時最後一個參數控制的,所以它通過pefree可以正確的根據內存類型進行相應的釋放。

問題就出在array類型, 內部釋放哈希桶的內存使用的是efree而不是pefree:

不僅是array類型,其實reference類型也是寫死了efree的:

所以說, 並不能直接用於持久化 的value析構函數。

因為在我的業務場景中, 保存的value只有string和array兩種類型,並且嵌套的array也是保存的string或array類型,所以我的dtor函數只覆蓋了所需的類型:

這個函數基本參照了 ,先減少1個引用計數,如果減少為0就進行資源釋放,對於string直接調用對應的api,而對於array則調用另一個api叫做 ,它內部會區分內存的類型進行釋放:

和 原理類似,持久化的 會有所標記,從而控制pefree的釋放行為。

只會將桶內所有key和value進行dtor析構,然後釋放哈希桶內存,並不會釋放zend_array結構自身的內存,所以我接著調用了pefree釋放它自身。

那麼,代碼中在else部分提到的」回收循環引用」是什麼意思呢?為什麼我注釋掉了呢?

所謂」循環引用」,是指這樣的一個例子:

我有一個 的zval1,我擁有唯一的引用計數=1。

接著,指定 ,value就是zval1自身,將其zendhashupdate保存到zval1內,按照規矩我會為value增加1個引用計數,這樣才算將value託付給了 ,所以將導致zval1的引用計數為2。

某個時刻,我們不再想訪問zval1,所以釋放1個引用計數,結果還剩下1個計數,並沒有觸發 的調用,這個zval1將永遠沒有機會被徹底釋放。

究其原因,就是因為zval1保存了zval1,導致循環引用,GC垃圾回收無法生效。

上面這段C操作,對應到PHP里就是這樣的代碼:

難道這樣的代碼,PHP的GC就無能為力了嗎?顯然不是。else里的注釋的代碼,其實就是用來針對這種情況的,而這種情況只能出現在zval1的類型是array或者object的情況下,因為只有它們內部才能保存其他變數,從而導致出現循環引用。

至於else部分的代碼是如何搞定循環引用的,你可以參考這篇博客: GC垃圾回收 。

原理並不算複雜,當我們的dtor函數發現減少1個引用計數後仍舊不為0的情況下,就會檢測這是否是因為循環引用引起,所以進入檢測函數 。

檢測的大概原理是:在我們的例子中,既然剩餘的1個引用計數是來自內部(子級)保存的自身,那麼就深度遍歷(因為孩子可能又循環引用了任意父級)它的孩子,將路過的zval的引用計數減1,如果在遍歷的回溯路徑上某個zval的引用計數減少為0,說明它的某個孩子引用了自己,現在可以釋放它。

最後 在擴展退出前,記得釋放一下持久化的zend_array:

更多分享,敬請關注

本文來源網路,侵立刪!


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PHP技術大全 的精彩文章:

TAG:PHP技術大全 |