PHP 共享內存使用與信號控制

共享內存

共享內存的使用主要是爲了可以在同一臺機器不一樣的進程中共享一些數據,好比在多個 php-fpm 進程中共享當前進程的使用狀況。這種通訊也稱爲進程間通訊(Inter-Process Communication),簡稱 IPC。php

PHP 內置的 shmop 擴展 (Shared Memory Operations) 提供了一系列共享內存操做的函數(多是用的人很少吧,這一起的文檔尚未中文翻譯)。在 Linux 上,這些函數直接是經過調用 shm* 系列的函數實現,而 Winodows 上也經過對系統函數的封裝實現了一樣的調用。html

主要函數:node

與此相關的還有一個很重要的函數:ftok,經過文件的 inode 信息(*nix 上經過 statls -i 命令查看)建立 IPC 的惟一 key(文件/文件夾的 inode 是惟一的)。這個函數在 Linux 上也是直接調用同名的系統函數實現,Windows 上仍是使用一些封裝。this

一個簡單的計數例子:

<?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 <cartus@atrior.de>, 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 關閉資源會出現沒法打開共享內存的錯誤警告。這應該是由於正在共享內存被上一次操做佔用中尚未釋放致使。

相關文章
相關標籤/搜索