管理全局狀態(tài)
在命令式語言中總是需要一些全局空間。在編程 PHP 或擴展時,我們將明確區(qū)分我們所稱的請求綁定全局變量和真正的全局變量。
請求全局變量是處理請求過程中需要攜帶和記憶信息的全局變量。一個簡單的例子是,您要求用戶在函數(shù)參數(shù)中提供一個值,并且希望能夠在其他函數(shù)中使用它。除了這條信息在幾個 PHP 函數(shù)調(diào)用中 “保持其值” 之外,它只為當前請求保留該值。下一個來的請求應(yīng)該什么都不知道。PHP 提供了一種機制來管理請求全局變量,不管選擇了什么樣的多處理模型,我們將在本章后面詳細介紹這一點。
真正的全局變量是跨請求保留的信息片段。這些信息通常是只讀的。如果您需要寫入這樣的全局變量作為請求處理的一部分,那么 PHP 無法幫助您。如果您使用 線程作為多處理模型, 您需要自己執(zhí)行內(nèi)存鎖。如果你使用 進程作為多處理模型, 您需要使用自己的IPC(進程間通信)。但是,在PHP擴展編程中不應(yīng)該出現(xiàn)這種情況。
相關(guān)學習推薦:PHP編程從入門到精通
管理請求全局變量
下面是一個使用請求全局的簡單擴展例子:
/* 真正的 C 全局 */ static zend_long rnd = 0; static void pib_rnd_init(void) { /* 在 0 到 100 之間隨機一個數(shù)字 */ php_random_int(0, 100, &rnd, 0); } PHP_RINIT_FUNCTION(pib) { pib_rnd_init(); return SUCCESS; } PHP_FUNCTION(pib_guess) { zend_long r; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { return; } if (r == rnd) { /* 將數(shù)字重置以進行猜測 */ pib_rnd_init(); RETURN_TRUE; } if (r < rnd) { RETURN_STRING("more"); } RETURN_STRING("less"); } PHP_FUNCTION(pib_reset) { if (zend_parse_parameters_none() == FAILURE) { return; } pib_rnd_init(); }
如你所見,這個擴展在請求開始時挑選一個隨機整型數(shù),之后通過pib_guess()
可以嘗試猜到這個數(shù)組。一旦猜到,該數(shù)字將重置。如果用戶想要手動重置數(shù)字,它也可以自己手動調(diào)用pib_reset()
去重置數(shù)值。
該隨機數(shù)作為一個 C 全局變量實現(xiàn)。如果 PHP 在進程中作為多進程模型的一部分使用不再是個問題,如果之后使用線程,這是不行的。
注意
作為提醒,你無需掌握將要使用哪種多進程模型。當你設(shè)計擴展時,你必須為這兩種模型做好準備。
當使用線程,會針對服務(wù)器中的每個線程共享一個 C 全局變量。例如我們上面的例子,網(wǎng)絡(luò)服務(wù)器的每個并行用戶將共享同一個數(shù)值。一些可能會一開始就重置數(shù)值,而其他則嘗試去猜測它。簡而言之,你清楚地了解了線程的關(guān)鍵問題。
我們必須持久化數(shù)據(jù)到同一請求,即使運行 PHP 多進程模型會利用線程,也必須讓它綁定到當前請求中。
使用 TSRM 宏來保護全局空間
PHP 設(shè)計了可以幫助擴展和內(nèi)核開發(fā)人員處理全局請求的層。該層稱為TSRM (線程安全資源管理) ,并且作為一組宏公開,你必須在任何需要訪問請求綁定全局(讀和寫)的時候使用該宏。
在多進程模型使用流程的情況下,在后臺,這些宏將解析為類似我們上面顯示的代碼。如我們所見,如果不適用線程,上面的代碼是完全有效的。所以,當使用進程時,這些宏將被擴展為類似的宏。
首先你要要做的就是聲明一個結(jié)構(gòu),它將是你所有全局變量的根:
ZEND_BEGIN_MODULE_GLOBALS(pib) zend_long rnd; ZEND_END_MODULE_GLOBALS(pib) /* 解析為 : * * typedef struct _zend_pib_globals { * zend_long rnd; * } zend_pib_globals; */
然后,創(chuàng)建一個這樣的全局變量:
ZEND_DECLARE_MODULE_GLOBALS(pib) /* 解析為 zend_pib_globals pib_globals; */
現(xiàn)在,你可以使用全局宏訪問器訪問數(shù)據(jù)。這個宏是由框架創(chuàng)建的,它應(yīng)該在你的 php_pib.h 頭文件定義。這看起來是這樣的:
#ifdef ZTS #define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v) #else #define PIB_G(v) (pib_globals.v) #endif
如你所見,如果沒有啟用 ZTS 模式,即編譯非線程安全的 PHP 和擴展(我們稱之為 NTS模式:非線程安全),宏只是解析到結(jié)構(gòu)中聲明的數(shù)據(jù)。因此,有以下變化:
static void pib_rnd_init(void) { php_random_int(0, 100, &PIB_G(rnd), 0); } PHP_FUNCTION(pib_guess) { zend_long r; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { return; } if (r == PIB_G(rnd)) { pib_rnd_init(); RETURN_TRUE; } if (r < PIB_G(rnd)) { RETURN_STRING("more"); } RETURN_STRING("less"); }
注意
當使用一個進程模型,TSRM 宏解析為對 C 全局變量的訪問。
當使用線程時,即當你編譯 ZTS PHP,事情變得更復(fù)雜。然后,我們看到的所有宏都解析為一些完全不同的東西,在這里很難解釋?;旧希斒褂?ZTS 編譯時,TSRM 使用 TLS(線程本地存儲)執(zhí)行了一項艱難的工作。
注意
簡而言之,當在 ZTS 編譯時,全局變量將綁定到當前線程。而在 NTS 編譯時,全局變量將綁定到當前進程上。TSRM 宏處理這項艱難的工作。你可能對運作方式感興趣,瀏覽 PHP 源代碼的/TSRM 目錄了解