標籤:extension nat 資料類型 操作 open comm mem ref 取值
共用記憶體
共用記憶體的使用主要是為了能夠在同一台機器不同的進程中共用一些資料,比如在多個 php-fpm 進程中共用當前進程的使用方式。這種通訊也稱為處理序間通訊(Inter-Process Communication),簡稱 IPC。
PHP 內建的 shmop 擴充 (Shared Memory Operations) 提供了一系列共用記憶體操作的函數(可能是用的人不多吧,這一塊兒的文檔還沒有中文翻譯)。在 Linux 上,這些函數直接是通過調用 shm* 系列的函數實現,而 Winodows 上也通過對系統函數的封裝實現了同樣的調用。
主要函數:
- shmop_close — 關閉共用記憶體塊
- shmop_delete — 刪除共用記憶體塊
- shmop_open — 建立或開啟共用記憶體塊
- shmop_read — 從共用記憶體塊中讀取資料
- shmop_size — 擷取共用記憶體塊的大小
- shmop_write — 向共用記憶體塊中寫入資料
與此相關的還有一個很重要的函數:ftok,通過檔案的 inode 資訊(*nix 上通過 stat
或 ls -i
命令查看)建立 IPC 的唯一 key(檔案/檔案夾的 inode 是唯一的)。這個函數在 Linux 上也是直接調用同名的系統函數實現,Windows 上還是使用一些封裝。
一個簡單的計數例子:
<?php# 建立一塊共用記憶體$shm_key = ftok(__FILE__, ‘t‘);$shm_id = shmop_open($shm_key, ‘c‘, 0644, 8);# 讀取並寫入資料$count = (int) shmop_read($shm_id, 0, 8) + 1;shmop_write($shm_id, str_pad($count, 8, ‘0‘, STR_PAD_LEFT), 0);// echo shmop_read($shm_id, 0, 8);# 關閉記憶體塊,並不會刪除共用記憶體,只是清除 PHP 的資源shmop_close($shm_id);
以上這段代碼沒執行一次計數加 1,而且資料是在不同進程之間共用的。也就是說除非手動刪除這塊記憶體使用量,否則這個資料是不會重設的。
有個需要稍微注意的點:shmop_open
的第二個參數是個 flag,類似 fopen 的第二個參數,其取值有以前幾個:
- “a” 唯讀訪問;
- “c” 如果記憶體片段不存在,則建立,如果存在,則可讀寫;
- “w” 讀寫;
- “n” 建立新的記憶體片段,如果同樣 key 的已存在,則會建立失敗,這是為了安全使用共用記憶體考慮。
此外,由於使用的共用記憶體片段是固定長度的,在儲存和讀取的時候要計算好資料的長度,不然可能會寫入失敗或者讀取空值。
訊號控制
既然上面使用到了共用記憶體儲存資料,就需要考慮是否有多個進程同時寫入資料到共用記憶體的情況,是否需要避免衝突。如果是這樣,就需要引入訊號量進行控制。
PHP 也提供了類似的內建擴充 sysvsem(這個擴充在 Windows 環境下沒有,文檔中將 ftok
函數也歸到這個擴充中,但實際上 ftok
是在標準函數庫中提供的,所以在 Windows 下也是可用的)。
在說訊號量控制之前,先說另外一件有意思的事情:看官方文檔你會發現這裡同樣也有共用記憶體操作的函數(shm_*
),因為這其實是同一類別(或者說來自於同一作者)的三個擴充,還有一個是 sysvmsg(隊列訊息) 。函數的實現上稍有差別,但實際做的事情基本相同。這和上文的 shmop 擴充有什麼區別呢?shmop 源碼下的 README
檔案有簡單的說明:
PHP already had a shared memory extension (sysvshm) written by Christian Cartus [email protected], unfortunately this extension was designed with PHP only in mind and offers high level features which are extremely bothersome for basic SHM we had in mind.
簡單說來:sysvshm 擴充提供的方法並不是原封不動的儲存使用者的資料,而是先使用 PHP 的變數序列化函數對參數進行序列化然後再進行儲存。這就導致通過這些方法儲存的資料無法和非 PHP 進程共用。不過這樣也能儲存更豐富的 PHP 資料類型,上文的擴充中 shmop_write
只能寫入字串。那麼為什麼 sysvshm 同樣不支援 Windows 呢?因為其並沒有引入封裝了 shm*
系列函數的 tsrm_win32.h
的標頭檔。
引入訊號控制之後的樣本:
<?php$id_key = ftok(__FILE__, ‘t‘);$sem_id = sem_get($id_key);# 請求訊號控制權if (sem_acquire($sem_id)) { $shm_id = shmop_open($id_key, ‘c‘, 0644, 8); # 讀取並寫入資料 $count = (int) shmop_read($shm_id, 0, 8) + 1; shmop_write($shm_id, str_pad($count, 8, ‘0‘, STR_PAD_LEFT), 0); // echo shmop_read($shm_id, 0, 8); # 關閉記憶體塊 shmop_close($shm_id); # 釋放訊號 sem_release($sem_id);}
但是本地想類比實現寫入衝突實際上是非常難的(考慮到電腦的執行速度)。在本地測試中,使用 for
迴圈操作時如果不使用 shmop_close
關閉資源會出現無法開啟共用記憶體的錯誤警告。這應該是因為正在共用記憶體被上一次操作佔用中還沒有釋放導致。
PHP 共用記憶體使用量與訊號控制