php之管理全域狀態
管理全域狀態
#在指令式語言中總是需要一些全域空間。在編程 PHP 或擴充時,我們將明確區分我們所稱的請求綁定全域變數和真正的全域變數。
請求全域變數是處理請求過程中需要攜帶和記憶訊息的全域變數。一個簡單的例子是,您要求使用者在函數參數中提供一個值,並且希望能夠在其他函數中使用它。除了這條資訊在幾個 PHP 函數呼叫中 “保持其值” 之外,它只為當前請求保留該值。下一個來的請求應該什麼都不知道。 PHP 提供了一種機制來管理請求全域變量,不管選擇了什麼樣的多處理模型,我們將在本章後面詳細介紹這一點。
真正的全域變數是跨請求保留的資訊片段。這些資訊通常是唯讀的。如果您需要寫入這樣的全域變數作為請求處理的一部分,那麼 PHP 無法幫助您。如果您使用 線程作為多處理模型, 您需要自己執行記憶體鎖定。如果你使用 進程作為多處理模型, 您需要使用自己的IPC(進程間通訊)。但是,在PHP擴展編程中不應該出現這種情況。
相關學習推薦: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 多進程模型會利用線程,也必須讓它綁定到目前請求中。
使用 TSRM 巨集來保護全域空間
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之管理全域狀態的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7
