科技 · 實用指南
mmap 無法關閉匯出指標?修改陣列前先追蹤每個借用者
父視圖已釋放或陣列名稱已刪除,切片與別名仍可能存活。先證明結果借用映射,還是擁有獨立副本。

mmap.close() 出現 BufferError,可能正說明某個借用者仍需要這段記憶體。僅靠更換陣列建構方式消除例外,無法證明生命週期安全。
先畫出參照關係:映射、直接視圖、衍生視圖,以及每個保留的陣列參照。八組本機對照區分刪除名稱、結束借用與建立獨立資料。
1. 明確定義所有者與傳出的借用者
記錄 Python、NumPy 版本、映射建構方式、陣列 dtype,以及結果從何處傳出。np.frombuffer 建立的是輸入物件的視圖。陣列基本切片可能產生另一個視圖,alias = arr 則只是為同一陣列增加名稱。兩者皆不保證獨立的資料緩衝區。
清理前先選擇 API 契約。借用結果要求映射所有者存活至所有使用端完成;需要比所有者存活更久的結果,應具備獨立儲存空間。唯讀描述透過該視圖允許哪些寫入,不會將借用記憶體變成自有副本。
2. 八組本機對照發現了什麼
測試在 Linux 上使用 CPython 3.12.14、NumPy 2.3.5,每組新建八位元組匿名映射,內容為 0 到 7。直接 memoryview 在 release() 前阻止關閉。父視圖釋放後,切片視圖仍阻止關閉;釋放切片後才能關閉。衍生的唯讀 memoryview 亦相同。
np.frombuffer(mm,dtype=np.uint8) 建立的陣列存活時,關閉遭阻止。刪除 arr 後保留 arr[:2] 切片,仍無法關閉;保留 alias = arr 也一樣。在這些簡單的 CPython 樣本中,移除最後的 NumPy 借用參照後才可關閉。這不代表在有快取、容器、閉包或循環參照的程式碼中,del arr 就能普遍保證清理完成。
3. 副本改變生命週期契約
第七組以 owned = arr.copy() 建立獨立 uint8 陣列。刪除借用陣列後,映射關閉,owned.tolist() 仍回傳 0 到 7。第八組以 owned = mm[:2] 取得獨立 bytes;關閉後仍保留 0 與 1。這些是複製結果,需要額外配置記憶體,也不再與映射持續同步。
來源資料有效時才複製,且只複製必須傳出的區域。刪除一個區域名稱不能讓其他呼叫端的別名失效。不要手動釋放 NumPy 陣列內部的 base 參照鏈、壓制關閉失敗,或僅為繞過保護而更換建構方式。若必須零複製,應透過明確的應用程式契約延長所有者生命週期。
4. 分別驗證關閉與保留的資料
對 memoryview,應在明確範圍內結束每個保留的子視圖與父視圖;上下文管理器僅能管理實際納入的視圖。對 NumPy,應在關閉所有者前結束所有借用切片與別名。失敗時檢查物件參照關係,不要反覆嘗試關閉,也不要假定記憶體回收已證明沒有借用者。
八組測試皆斷言在各自明確清理後成功關閉。關閉後只讀取獨立副本,未在解除映射後解參照借用指標。本次未測試真實檔案、共享記憶體 unlink、並行使用端或其他平台。應用程式中應分別驗證預期關閉路徑與複製結果值。檔案刷新與持久保存屬於另一項契約,無法解決尚存的匯出視圖。
