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