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:
更多分享,敬請關注
本文來源網路,侵立刪!
TAG:PHP技術大全 |