首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 進程間共享內存完整指南:shmop + 信號量實現高性能 IPC

     編輯:PHP 綜合知識

在單台服務器上,多個 PHP 進程(如 CLI Worker、Web 請求、定時任務)需要高性能數據共享時,系統 V 共享內存 + 信號量是原生、高效、無外部依賴的方案。本文完整講解 shmop 操作、信號量同步、實戰封裝。

環境要求

# PHP 編譯安裝必須啟用
./configure --enable-shmop --enable-sysvsem --enable-sysvshm

# 驗證
php -m | grep -E 'shmop|sysv'

核心概念

概念函數族作用
共享內存shmop_*進程間數據存儲,字節級讀寫
信號量sem_*互斥鎖,保證併發安全
消息隊列msg_*進程間通信(本文不展開)

共享內存基礎操作

1. 創建/打開共享內存段

$key = ftok(__FILE__, 's');  // 邏輯鍵值,同鍵值進程共享同一段
$mode = 'c';  // c=創建, a=訪問
$perm = 0644; // 權限
$size = 1024 * 1024; // 1MB

$shmId = shmop_open($key, $mode, $perm, $size);
if (!$shmId) {
    die("創建共享內存失敗");
}

// 獲取系統 IPC 狀態:ipcs -m

2. 寫入數據

$data = "Hello Shared Memory " . microtime(true);
$bytes = shmop_write($shmId, $data, 0);  // 偏移 0 開始寫入
echo "寫入 $bytes 字節";

3. 讀取數據

$data = shmop_read($shmId, 0, $bytes);  // 從 0 讀取 $bytes 長度
echo "讀取: $data";

4. 刪除共享內存

shmop_delete($shmId);  // 標記刪除,最後一個進程 detach 時真正釋放
shmop_close($shmId);   // 關閉當前進程連接

信號量實現互斥鎖

創建/獲取信號量

$semKey = ftok(__FILE__, 'm');  // 不同於 shm key
$semId = sem_get($semKey, 1, 0644);  // max_acquire=1 互斥鎖

// 獲取鎖(阻塞等待)
sem_acquire($semId);

// 釋放鎖
sem_release($semId);

完整封裝類:安全的共享內存操作

class SharedMemory {
    private $shmId;
    private $semId;
    private $size;
    
    public function __construct($name, $size = 1048576) {  // 默認 1MB
        $this->size = $size;
        $key = ftok(__FILE__, $name[0]);
        $semKey = ftok(__FILE__, $name[1] ?? 'm');
        
        $this->shmId = shmop_open($key, 'c', 0644, $size);
        $this->semId = sem_get($semKey, 1, 0644);
        
        if (!$this->shmId || !$this->semId) {
            throw new Exception("IPC 資源創建失敗");
        }
    }
    
    // 寫入(自動加鎖、序列化)
    public function write($key, $value) {
        $this->acquire();
        
        try {
            $data = $this->readAll();
            $data[$key] = $value;
            $serialized = serialize($data);
            
            if (strlen($serialized) > $this->size) {
                throw new Exception("數據超過共享內存大小");
            }
            
            shmop_write($this->shmId, $serialized, 0);
        } finally {
            $this->release();
        }
    }
    
    // 讀取(自動加鎖、反序列化)
    public function read($key, $default = null) {
        $this->acquire();
        try {
            $data = $this->readAll();
            return $data[$key] ?? $default;
        } finally {
            $this->release();
        }
    }
    
    // 批量讀取所有
    public function readAll() {
        $this->acquire();
        try {
            $raw = shmop_read($this->shmId, 0, $this->size);
            $data = @unserialize($raw);
            return is_array($data) ? $data : array();
        } finally {
            $this->release();
        }
    }
    
    // 刪除鍵
    public function delete($key) {
        $this->acquire();
        try {
            $data = $this->readAll();
            unset($data[$key]);
            shmop_write($this->shmId, serialize($data), 0);
        } finally {
            $this->release();
        }
    }
    
    private function acquire() {
        sem_acquire($this->semId);
    }
    
    private function release() {
        sem_release($this->semId);
    }
    
    public function __destruct() {
        if ($this->shmId) shmop_close($this->shmId);
    }
}

// 使用示例
$shm = new SharedMemory('myapp');

// 進程 A 寫入
$shm->write('counter', 0);
for ($i=0; $i<1000; $i++) {
    $val = $shm->read('counter');
    $shm->write('counter', $val + 1);
}

// 進程 B 讀取
echo $shm->read('counter');  // 1000

進階:計數器原子操作(無需完整序列化)

class AtomicCounter {
    private $shmId;
    private $semId;
    private $offset;
    
    public function __construct($name, $offset = 0) {
        $key = ftok(__FILE__, $name[0]);
        $semKey = ftok(__FILE__, $name[1] ?? 'm');
        $this->shmId = shmop_open($key, 'c', 0644, 8);  // 8 字節存 int64
        $this->semId = sem_get($semKey, 1, 0644);
        $this->offset = $offset * 8;
    }
    
    public function increment($step = 1) {
        sem_acquire($this->semId);
        try {
            $binary = shmop_read($this->shmId, $this->offset, 8);
            $value = unpack('Q', $binary)[1] ?? 0;
            $value += $step;
            shmop_write($this->shmId, pack('Q', $value), $this->offset);
            return $value;
        } finally {
            sem_release($this->semId);
        }
    }
    
    public function get() {
        sem_acquire($this->semId);
        try {
            $binary = shmop_read($this->shmId, $this->offset, 8);
            return unpack('Q', $binary)[1] ?? 0;
        } finally {
            sem_release($this->semId);
        }
    }
}

系統監控與調試

# 查看所有共享內存段
ipcs -m

# 查看信號量
ipcs -s

# 刪除殘留資源 (謹慎!)
ipcrm -m 
ipcrm -s 

# 關鍵參數調優 (/etc/sysctl.conf)
kernel.shmmax = 134217728   # 最大段 128MB
kernel.shmall = 32768       # 總頁數
kernel.sem = 250 32000 100 128  # 信號量限制

方案對比與選型

方案性能持久化跨進程網絡透明複雜度
shmop + sem極高 (內存級)進程存活期✅❌中
Redis高 (網絡)持久化可選✅✅低
文件鎖 flock低 (磁盤)持久✅❌低
數據庫中持久✅✅低
APCu極高請求級/進程級同進程池❌極低

最佳實踐總結

  1. 鍵值生成:使用 ftok() 避免衝突,不要硬編碼數字
  2. 必須加鎖:讀寫分離場景也要用信號量,避免髒讀
  3. 異常安全:try/finally 保證釋放鎖
  4. 大小規劃:預估數據量,shmop_open 不支持擴容
  5. 清理機制:進程退出調用 shmop_close,定期 ipcs 檢查殘留
  6. 避免大對象:序列化開銷大,計數器用二進制打包

更多高併發技術:《PHP 多進程編程實戰》、《Linux IPC 編程指南》

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved