概述
共用記憶體是一種在相同機器中的應用程式之間交換資料的有效方式。一個進程可建立一個可供其他進程訪問的記憶體段,只要它分配了正確的許可權。每個記憶體段擁有一個惟一的 ID(稱為 shmid),這個 ID 指向一個實體記憶體地區,其他進程可在該地區操作它。建立並提供了合適的許可權之後,同一台機器中的其他進程就可以操作這些記憶體段:讀取、寫入和刪除。
這表明使用 C 語言編寫的應用程式可與使用其他語言(比如 Java™ 或 PHP)編寫的應用程式共用資訊。它們都可以共用資訊,只要它們可訪問和理解該資訊。共用記憶體在針對大部分語言的實現中得到了廣泛使用,所以訪問應該不是問題。要理解資訊,我們可以使用一種標準格式,比如 XML 或 JSON。
共用記憶體的使用是一種在進程之間交換資料的快速方法,主要因為在建立記憶體段之後傳遞資料,不會涉及核心。這種方法常常稱為處理序間通訊 (IPC)。其他 IPC 方法包括管道、訊息佇列、RPC 和通訊端。當使用需要彼此通訊的應用程式的生態系統時,這種在應用程式之間快速、可靠地交換資料的能力非常有用。取決於生態系統的大小,使用資料庫在應用程式之間交換資訊的常用方法常常會導致查詢緩慢,甚至 I/O 阻塞。使用共用記憶體,沒有 I/O 會減緩開發人員的進度。
本文的提議非常簡單,學習如何使用 PHP 建立和操作共用記憶體段,使用它們儲存可供其他應用程式使用的資料集。即使沒有使用共用記憶體交換資料的計劃,它本身也在許多好處,因為它使應用程式能夠遠離 I/O 問題。將資料集直接儲存在記憶體中具有諸多優勢,從 Web 服務資料緩衝到會話共用。它是一個非常有用的概念,每個 PHP 開發人員都應該知道。
共用記憶體和 PHP
PHP 擁有豐富的可用擴充,共用記憶體也一樣。使用一些共用的函數,無需安裝任何擴充,開發人員就能夠輕鬆操作記憶體段。
建立記憶體段
共用記憶體函數類似於檔案操作函數,但無需處理一個流,您將處理一個共用記憶體訪問 ID。第一個樣本就是 shmop_open 函數,它允許您開啟一個現有的記憶體段或建立一個新記憶體段。此函數非常類似於經典的 fopen 函數,後者開啟用於檔案操作的流,返回一個資源供其他希望讀取或寫入該開啟的流的函數使用。讓我們看看清單 1 中的 shmop_open。
清單 1. shmop_open 函數
複製代碼 代碼如下:
<?php
$systemid = 864; // System ID for the shared memory segment
$mode = "c"; // Access mode
$permissions = 0755; // Permissions for the shared memory segment
$size = 1024; // Size, in bytes, of the segment
$shmid = shmop_open($systemid, $mode, $permissions, $size);
?>
該函數中出現的第一個事物是系統 ID 參數。這是標識系統中的共用記憶體段的數字。第二個參數是訪問模式,它非常類似於 fopen 函數的訪問模式。您可以在 4 種不同的模式下訪問一個記憶體段:
•模式 “a”,它允許您訪問唯讀記憶體段
•模式 “w”,它允許您訪問可讀寫的記憶體段
•模式 “c”,它建立一個新記憶體段,或者如果該記憶體段已存在,嘗試開啟它進行讀寫
•模式 “n”,它建立一個新記憶體段,如果該記憶體段已存在,則會失敗
第三個參數是記憶體段的許可權。您必須在這裡提供一個八進位值。
第四個參數提供記憶體段大小,以位元組為單位。在寫入一個記憶體段之前,您必須在它之上分配適當的位元組數。
請注意,此函數返回一個 ID 編號,其他函數可使用該 ID 編號操作該共用記憶體段。這個 ID 是共用記憶體訪問 ID,與系統 ID 不同,它以參數的形式傳遞。請注意不要混淆這兩者。如果失敗,shmop_open 將返回 FALSE。
向記憶體段寫入資料
使用 shmop_write 函數向共用記憶體塊寫入資料。此函數的使用很簡單,它僅接受 3 個參數,如清單 2 所示。
清單 2. 使用 shmop_write 向共用記憶體塊寫入資料
複製代碼 代碼如下:
<?php
$shmid = shmop_open(864, 'c', 0755, 1024);
shmop_write($shmid, "Hello World!", 0);
?>
這個函數類似於 fwrite 函數,後者有兩個參數:開啟的流資源(由 fopen 返回)和您希望寫入的資料。shmop_write 函數也執行此任務。
第一個參數是 shmop_open 返回的 ID,它識別您操作的共用記憶體塊。第二個參數是您希望儲存的資料,最後的第三個參數是您希望開始寫入的位置。預設情況下,我們始終使用 0 來表示開始寫入的位置。請注意,此函數在失敗時會返回 FALSE,在成功時會返回寫入的位元組數。
從記憶體段讀取資料
從共用記憶體段讀取資料很簡單。您只需要一個開啟的記憶體段和 shmop_read 函數。此函數接受一些參數,工作原理類似於 fread。參見清單 3,讀取一個 PHP 檔案的內容。
清單 3. 使用 shmop_read 讀取一個檔案的內容
複製代碼 代碼如下:
<?php
$stream = fopen('file.txt', 'r+');
fwrite($stream, "Hello World!");
echo fread($stream, 11);
?>
讀取共用記憶體段的內容的過程與此類似,如清單 4 所示:
清單 4. 讀取共用記憶體段的內容
複製代碼 代碼如下:
<?php
$shmid = shmop_open(864, 'c', 0755, 1024);
shmop_write($shmid, "Hello World!", 0);
echo shmop_read($shmid, 0, 11);
?>
請留意這裡的參數。shmop_read 函數將接受 shmop_open 返回的 ID,我們已知道它,不過它還接受另外兩個參數。第二個參數是您希望從記憶體段讀取的位置,而第三個是您希望讀取的位元組數。第二個參數可以始終為 0,表示資料的開頭,但第三個參數可能存在問題,因為我們不知道我們希望讀取多少位元組。
這非常類似於我們在 fread 函數中的行為,該函數接受兩個參數:開啟的流資源(由 fopen 返回)和您希望從該流讀取的位元組數。使用filesize 函數(它返回一個檔案中的位元組數)來完整地讀取它。
幸運的是,當使用共用記憶體段時,shmop_size 函數返回一個記憶體段的大小(以位元組為單位),類似於 filesize 函數。參見清單 5。
清單 5. shmop_size 函數返回記憶體段大小,以位元組為單位
複製代碼 代碼如下:
<?php
$shmid = shmop_open(864, 'c', 0755, 1024);
shmop_write($shmid, "Hello World!", 0);
$size = shmop_size($shmid);
echo shmop_read($shmid, 0, $size);
?>
回頁首
刪除記憶體段
我們學習了如何開啟、寫入和讀取共用記憶體段。要完成我們的 CRUD 類,我們還需要學習如何刪除記憶體段。該任務可使用 shmop_delete 函數輕鬆完成,該函數僅接受一個參數:我們希望刪除的共用記憶體 ID。
清單 6. shmop_delete 標記要刪除的記憶體段
複製代碼 代碼如下:
<?php
$shmid = shmop_open(864, 'c', 0755, 1024);
shmop_write($shmid, "Hello World!", 0);
shmop_delete($shmid);
?>
這不會實際刪除該記憶體段。它將該記憶體區段標記為刪除,因為共用記憶體段在有其他進程正在使用它時無法被刪除。shmop_delete 函數將該記憶體區段標記為刪除,阻止任何其他進程開啟它。要刪除它,我們需要關閉該記憶體段。
關閉記憶體段
開啟一個共用記憶體段會 “附加” 到它。附加該記憶體段之後,我們可在其中進行讀取和寫入,但完成操作後,我們必須從它解除。這使用清單 7 中的 shmop_close 函數來完成。
這非常類似於處理檔案時的 fclose 函數。開啟包含一個檔案的流並在其中讀取或寫入資料後,我們必須關閉它,否則將發生鎖定。
清單 7. 使用 shmop_close 與一個記憶體段分開
複製代碼 代碼如下:
<?php
$shmid = shmop_open(864, 'c', 0755, 1024);
shmop_write($shmid, "Hello World!", 0);
shmop_delete($shmid);
shmop_close($shmid);
?>
使用共用記憶體作為一個儲存選項
有了共用記憶體和共用記憶體段上基本 CRUD 操作的基本知識,是時候應用此知識了。我們可以使用共用記憶體作為一種獨特的儲存選項,提供快速讀/寫操作和進程互通性等優勢。對於 Web 應用程式,這意味著:
•緩衝儲存(資料庫查詢、Web 服務資料、外部資料)
•會話儲存
•應用程式之間的資料交換
在繼續之前,我想介紹一個名為 SimpleSHM 小型庫。SimpleSHM 是一個較小的抽象層,用於使用 PHP 操作共用記憶體,支援以一種物件導向的方式輕鬆操作記憶體段。在編寫使用共用記憶體進行儲存的小型應用程式時,這個庫可協助建立非常簡潔的代碼。要瞭解 SimpleSHM,請訪問GitHub 頁面。
您可以使用 3 個方法進行處理:讀、寫和刪除。從該類中簡單地執行個體化一個對象,可以控制開啟的共用記憶體段。清單 8 展示了基本用途。
清單 8. SimpleSHM 基本用途
複製代碼 代碼如下:
<?php
$memory = new SimpleSHM;
$memory->write('Sample');
echo $memory->read();
?>
請注意,這裡沒有為該類傳遞一個 ID。如果沒有傳遞 ID,它將隨機播放一個編號並開啟該編號的新記憶體段。我們可以以參數的形式傳遞一個編號,供建構函式開啟現有的記憶體段,或者建立一個具有特定 ID 的記憶體段,如清單 9 所示。
清單 9. 開啟一個特定的記憶體段
複製代碼 代碼如下:
<?php
$new = new SimpleSHM(897);
$new->write('Sample');
echo $new->read();
?>
神奇的方法 __destructor 負責在該記憶體段上調用 shmop_close 來取消設定對象,以與該記憶體段分離。我們將這稱為 “SimpleSHM 101”。現在讓我們將此方法用於更進階的用途:使用共用記憶體作為儲存。儲存資料集需要序列化,因為數組或對象無法儲存在記憶體中。儘管這裡使用了 JSON 來序列化,但任何其他方法(比如 XML 或內建的 PHP 序列化功能)也已足夠。清單 10 給出了一個樣本。
清單 10. 使用共用記憶體作為儲存
複製代碼 代碼如下:
<?php
require('SimpleSHM.class.php');
$results = array(
'user' => 'John',
'password' => '123456',
'posts' => array('My name is John', 'My name is not John')
);
$data = json_encode($results);
$memory = new SimpleSHM;
$memory->write($data);
$storedarray = json_decode($memory->read());
print_r($storedarray);
?>
我們成功地將一個數組序列化為一個 JSON 字串,將它儲存在共用記憶體塊中,從中讀取資料,去序列化 JSON 字串,並顯示儲存的數組。這看起來很簡單,但請想象一下這個程式碼片段帶來的可能性。您可以使用它儲存 Web 服務請求、資料庫查詢或者甚至模板引擎緩衝的結果。在記憶體中讀取和寫入將帶來比在磁碟中讀取和寫入更高的效能。
使用此儲存技術不僅對緩衝有用,也對應用程式之間的資料交換也有用,只要資料以兩端都可讀的格式儲存。不要低估共用記憶體在 Web 應用程式中的力量。可採用許多不同的方式來巧妙地實現這種儲存,惟一的限制是開發人員的創造力和技能