您好,登錄后才能下訂單哦!
什么是php的管理全局狀態?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
在命令式語言中總是需要一些全局空間。在編程 PHP 或擴展時,我們將明確區分我們所稱的請求綁定全局變量和真正的全局變量。
請求全局變量是處理請求過程中需要攜帶和記憶信息的全局變量。一個簡單的例子是,您要求用戶在函數參數中提供一個值,并且希望能夠在其他函數中使用它。除了這條信息在幾個 PHP 函數調用中 “保持其值” 之外,它只為當前請求保留該值。下一個來的請求應該什么都不知道。PHP 提供了一種機制來管理請求全局變量,不管選擇了什么樣的多處理模型,我們將在本章后面詳細介紹這一點。
真正的全局變量是跨請求保留的信息片段。這些信息通常是只讀的。如果您需要寫入這樣的全局變量作為請求處理的一部分,那么 PHP 無法幫助您。如果您使用 線程作為多處理模型, 您需要自己執行內存鎖。如果你使用 進程作為多處理模型, 您需要使用自己的IPC(進程間通信)。但是,在PHP擴展編程中不應該出現這種情況。
下面是一個使用請求全局的簡單擴展例子:
/* 真正的 C 全局 */ static zend_long rnd = 0; static void pib_rnd_init(void) { /* 在 0 到 100 之間隨機一個數字 */ 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) { /* 將數字重置以進行猜測 */ 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(); }
如你所見,這個擴展在請求開始時挑選一個隨機整型數,之后通過pib_guess()
可以嘗試猜到這個數組。一旦猜到,該數字將重置。如果用戶想要手動重置數字,它也可以自己手動調用pib_reset()
去重置數值。
該隨機數作為一個 C 全局變量實現。如果 PHP 在進程中作為多進程模型的一部分使用不再是個問題,如果之后使用線程,這是不行的。
注意
作為提醒,你無需掌握將要使用哪種多進程模型。當你設計擴展時,你必須為這兩種模型做好準備。
當使用線程,會針對服務器中的每個線程共享一個 C 全局變量。例如我們上面的例子,網絡服務器的每個并行用戶將共享同一個數值。一些可能會一開始就重置數值,而其他則嘗試去猜測它。簡而言之,你清楚地了解了線程的關鍵問題。
我們必須持久化數據到同一請求,即使運行 PHP 多進程模型會利用線程,也必須讓它綁定到當前請求中。
PHP 設計了可以幫助擴展和內核開發人員處理全局請求的層。該層稱為TSRM (線程安全資源管理) ,并且作為一組宏公開,你必須在任何需要訪問請求綁定全局(讀和寫)的時候使用該宏。
在多進程模型使用流程的情況下,在后臺,這些宏將解析為類似我們上面顯示的代碼。如我們所見,如果不適用線程,上面的代碼是完全有效的。所以,當使用進程時,這些宏將被擴展為類似的宏。
首先你要要做的就是聲明一個結構,它將是你所有全局變量的根:
ZEND_BEGIN_MODULE_GLOBALS(pib) zend_long rnd; ZEND_END_MODULE_GLOBALS(pib) /* 解析為 : * * typedef struct _zend_pib_globals { * zend_long rnd; * } zend_pib_globals; */
然后,創建一個這樣的全局變量:
ZEND_DECLARE_MODULE_GLOBALS(pib) /* 解析為 zend_pib_globals pib_globals; */
現在,你可以使用全局宏訪問器訪問數據。這個宏是由框架創建的,它應該在你的 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模式:非線程安全),宏只是解析到結構中聲明的數據。因此,有以下變化:
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,事情變得更復雜。然后,我們看到的所有宏都解析為一些完全不同的東西,在這里很難解釋。基本上,當使用 ZTS 編譯時,TSRM 使用 TLS(線程本地存儲)執行了一項艱難的工作。
注意
簡而言之,當在 ZTS 編譯時,全局變量將綁定到當前線程。而在 NTS 編譯時,全局變量將綁定到當前進程上。TSRM 宏處理這項艱難的工作。你可能對運作方式感興趣,瀏覽 PHP 源代碼的/TSRM 目錄了解更多關于 PHP 線程安全。
有時,可能需要將全局變量初始化為一些默認值,通常為零。引擎幫助下的TSRM系統提供了一個鉤子來為您的全局變量提供默認值,我們稱之為GINIT。
注意
關于 PHP 掛鉤的完整信息,請參考 PHP 生命周期章節。
讓我們將隨機值設為零:
PHP_GSHUTDOWN_FUNCTION(pib) { } PHP_GINIT_FUNCTION(pib) { pib_globals->rnd = 0; } zend_module_entry pib_module_entry = { STANDARD_MODULE_HEADER, "pib", NULL, NULL, NULL, NULL, NULL, NULL, "0.1", PHP_MODULE_GLOBALS(pib), PHP_GINIT(pib), PHP_GSHUTDOWN(pib), NULL, /* PRSHUTDOWN() */ STANDARD_MODULE_PROPERTIES_EX };
我們選擇僅顯示 zend_module_entry
(和其他 NULL
)的相關部分。如你所見,全局管理掛鉤發生在結構的中間。首先是PHP_MODULE_GLOBALS()
來確定全局變量的大小,然后是我們的 GINIT
和 GSHUTDOWN
鉤子。然后我們使用了STANDARD_MODULE_PROPERTIES_EX
關閉結構,而不是STANDARD_MODULE_PROPERTIES
。只需以正確的方式完成結構即可,請參閱?:
#define STANDARD_MODULE_PROPERTIES NO_MODULE_GLOBALS, NULL, STANDARD_MODULE_PROPERTIES_EX
在GINIT
函數中,你傳遞了一個指向全局變量當前存儲位置的指針。你可以使用它來初始化全局變量。在這里,我們將零放入隨機值(雖然不是很有用,但我們接受它)。
警告
不要在 GINIT 中使用
PIB_G()
宏。使用你得到的指針。注意
對于當前進程,在
MINIT()
之前啟動了GINIT()
。如果是 NTS,就這樣而已。 如果是 ZTS,線程庫產生的每個新線程都會額外調用GINIT()
。警告
GINIT()
不作為RINIT()
的一部分被調用。如果你需要在每次新請求時清除全局變量,則需要像在本章所示示例中所做的那樣手動進行。
這是一個更高級的完整示例。如果玩家獲勝,則將其得分(嘗試次數)添加到可以從用戶區獲取的得分數組中。沒什么難的,得分數組在請求啟動時初始化,然后在玩家獲勝時使用,并在當前請求結束時清除:
ZEND_BEGIN_MODULE_GLOBALS(pib) zend_long rnd; zend_ulong cur_score; zval scores; ZEND_END_MODULE_GLOBALS(pib) ZEND_DECLARE_MODULE_GLOBALS(pib) static void pib_rnd_init(void) { /* 重置當前分數 */ PIB_G(cur_score) = 0; php_random_int(0, 100, &PIB_G(rnd), 0); } PHP_GINIT_FUNCTION(pib) { /* ZEND_SECURE_ZERO 是 memset(0)。也可以解析為 bzero() */ ZEND_SECURE_ZERO(pib_globals, sizeof(*pib_globals)); } ZEND_BEGIN_ARG_INFO_EX(arginfo_guess, 0, 0, 1) ZEND_ARG_INFO(0, num) ZEND_END_ARG_INFO() PHP_RINIT_FUNCTION(pib) { array_init(&PIB_G(scores)); pib_rnd_init(); return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(pib) { zval_dtor(&PIB_G(scores)); return SUCCESS; } PHP_FUNCTION(pib_guess) { zend_long r; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { return; } if (r == PIB_G(rnd)) { add_next_index_long(&PIB_G(scores), PIB_G(cur_score)); pib_rnd_init(); RETURN_TRUE; } PIB_G(cur_score)++; if (r < PIB_G(rnd)) { RETURN_STRING("more"); } RETURN_STRING("less"); } PHP_FUNCTION(pib_get_scores) { if (zend_parse_parameters_none() == FAILURE) { return; } RETVAL_ZVAL(&PIB_G(scores), 1, 0); } PHP_FUNCTION(pib_reset) { if (zend_parse_parameters_none() == FAILURE) { return; } pib_rnd_init(); } static const zend_function_entry func[] = { PHP_FE(pib_reset, NULL) PHP_FE(pib_get_scores, NULL) PHP_FE(pib_guess, arginfo_guess) PHP_FE_END }; zend_module_entry pib_module_entry = { STANDARD_MODULE_HEADER, "pib", func, /* 函數入口 */ NULL, /* 模塊初始化 */ NULL, /* 模塊關閉 */ PHP_RINIT(pib), /* 請求初始化 */ PHP_RSHUTDOWN(pib), /* 請求關閉 */ NULL, /* 模塊信息 */ "0.1", /* 替換為擴展的版本號 */ PHP_MODULE_GLOBALS(pib), PHP_GINIT(pib), NULL, NULL, STANDARD_MODULE_PROPERTIES_EX };
這里必須要注意的是,如果你希望在請求之間保持分數,PHP 不提供任何便利。而是需要一個持久的共享存儲,例如文件,數據庫,某些內存區域等。PHP 的設計目的不是將信息持久存儲在其內部的請求,因此它不提供這么做,但它提供了實用程序來訪問請求綁定的全局空間,如我們所示。
然后,很容易地在RINIT()
中初始化一個數組,然后在RSHUTDOWN()
中銷毀它。請記住,array_init
創建一個zend_array 并放入一個 zval。但這是免分配的,不要擔心分配用戶無法使用的數組(因此浪費分配),array_init()
非常廉價 (閱讀源代碼)。
當我們將這樣的數組返回給用戶時,我們不會忘記增加其引用計數(在 RETVAL_ZVAL
中),因為我們在擴展中保留了對此類數組的引用。
真實全局變量是非線程保護的真實C全局變量。有時可能會需要它們。但是請記住主要規則:在處理請求時,不能安全地寫入此類全局變量。因此,通常在 PHP 中,我們需要此類變量并將其用作只讀變量。
請記住,在 PHP 生命周期的MINIT()
或MSHUTDOWN()
步驟中編寫真實全局變量是絕對安全的。但是不能在處理請求時給他們寫入值(但可以從他們那里讀取)。
因此,一個簡單的示例是你想要讀取環境值以對其進行處理。此外,初始化持久性的 zend_string并在之后處理某些請求時加以利用是很常見的。
這是介紹真實全局變量的修補示例,我們僅顯示與先前代碼的差異,而不顯示完整代碼:
static zend_string *more, *less; static zend_ulong max = 100; static void register_persistent_string(char *str, zend_string **result) { *result = zend_string_init(str, strlen(str), 1); zend_string_hash_val(*result); GC_FLAGS(*result) |= IS_INTERNED; } static void pib_rnd_init(void) { /* 重置當前分數 */ PIB_G(cur_score) = 0; php_random_int(0, max, &PIB_G(rnd), 0); } PHP_MINIT_FUNCTION(pib) { char *pib_max; register_persistent_string("more", &more); register_persistent_string("less", &less); if (pib_max = getenv("PIB_RAND_MAX")) { if (!strchr(pib_max, '-')) { max = ZEND_STRTOUL(pib_max, NULL, 10); } } return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(pib) { zend_string_release(more); zend_string_release(less); return SUCCESS; } PHP_FUNCTION(pib_guess) { zend_long r; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { return; } if (r == PIB_G(rnd)) { add_next_index_long(&PIB_G(scores), PIB_G(cur_score)); pib_rnd_init(); RETURN_TRUE; } PIB_G(cur_score)++; if (r < PIB_G(rnd)) { RETURN_STR(more); } RETURN_STR(less); }
在這里我們創建了兩個 zend_string 變量 more
和 less
。這些字符串不需要像以前一樣在使用時立即創建和銷毀。這些是不可變的字符串,只要保持不變,就可以分配一次并在需要的任何時間重復使用(即只讀)。我們在zend_string_init()
中使用持久分配,在MINIT()
中初始化這兩個字符串,我們現在預先計算其哈希值(而不是先執行第一個請求),并且我們告訴 zval 垃圾收集器,這些字符串已被扣留,因此它將永遠不會嘗試銷毀它們(但是,如果將它們用作寫操作(例如連接)的一部分,則可能需要復制它們)。顯然我們不會忘記在MSHUTDOWN()
中銷毀這些字符串。
然后在MINIT()
中我們探查一個PIB_RAND_MAX
環境,并將其用作隨機數選擇的最大范圍值。由于我們使用無符號整數,并且我們知道strtoull()
不會抱怨負數(因此將整數范圍包裹為符號不匹配),我們只是避免使用負數(經典的libc解決方法)。
關于什么是php的管理全局狀態問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。