当前位置:首页 > PHP教程 > php应用 > 列表

php之管理全局状态

发布:smiling 来源: PHP粉丝网  添加日期:2022-07-18 16:34:02 浏览: 评论:0 

在命令式语言中总是需要一些全局空间,在编程 PHP 或扩展时,我们将明确区分我们所称的请求绑定全局变量和真正的全局变量。

请求全局变量是处理请求过程中需要携带和记忆信息的全局变量。一个简单的例子是,您要求用户在函数参数中提供一个值,并且希望能够在其他函数中使用它。除了这条信息在几个 PHP 函数调用中 “保持其值” 之外,它只为当前请求保留该值。下一个来的请求应该什么都不知道。PHP 提供了一种机制来管理请求全局变量,不管选择了什么样的多处理模型,我们将在本章后面详细介绍这一点。

真正的全局变量是跨请求保留的信息片段。这些信息通常是只读的。如果您需要写入这样的全局变量作为请求处理的一部分,那么 PHP 无法帮助您。如果您使用 线程作为多处理模型, 您需要自己执行内存锁。如果你使用 进程作为多处理模型, 您需要使用自己的IPC(进程间通信)。但是,在PHP扩展编程中不应该出现这种情况。

管理请求全局变量

下面是一个使用请求全局的简单扩展例子:

  1. /* 真正的 C 全局 */ 
  2.  
  3. static zend_long rnd = 0; 
  4.  
  5.  
  6.  
  7. static void pib_rnd_init(void) 
  8.  
  9.  
  10.     /* 在 0 到 100 之间随机一个数字 */ 
  11.  
  12.     php_random_int(0, 100, &rnd, 0); 
  13.  
  14.  
  15.  
  16.  
  17. PHP_RINIT_FUNCTION(pib) 
  18.  
  19.  
  20.     pib_rnd_init(); 
  21.  
  22.  
  23.  
  24.     return SUCCESS; 
  25.  
  26.  
  27.  
  28.  
  29. PHP_FUNCTION(pib_guess) 
  30.  
  31.  
  32.     zend_long r; 
  33.  
  34.  
  35.  
  36.     if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { 
  37.  
  38.         return
  39.  
  40.     } 
  41.  
  42.  
  43.  
  44.     if (r == rnd) { 
  45.  
  46.         /* 将数字重置以进行猜测 */ 
  47.  
  48.         pib_rnd_init(); 
  49.  
  50.         RETURN_TRUE; 
  51.  
  52.     } 
  53.  
  54.  
  55.  
  56.     if (r < rnd) { 
  57.  
  58.         RETURN_STRING("more"); 
  59.  
  60.     } 
  61.  
  62.  
  63.  
  64.     RETURN_STRING("less"); 
  65.  
  66.  
  67.  
  68.  
  69. PHP_FUNCTION(pib_reset) 
  70.  
  71.  
  72.     if (zend_parse_parameters_none() == FAILURE) { 
  73.  
  74.         return
  75.  
  76.     } 
  77.  
  78.  
  79.  
  80.     pib_rnd_init(); 
  81.  

如你所见,这个扩展在请求开始时挑选一个随机整型数,之后通过pib_guess()可以尝试猜到这个数组。一旦猜到,该数字将重置。如果用户想要手动重置数字,它也可以自己手动调用pib_reset() 去重置数值。

该随机数作为一个 C 全局变量实现。如果 PHP 在进程中作为多进程模型的一部分使用不再是个问题,如果之后使用线程,这是不行的。

注意

作为提醒,你无需掌握将要使用哪种多进程模型。当你设计扩展时,你必须为这两种模型做好准备。

当使用线程,会针对服务器中的每个线程共享一个 C 全局变量。例如我们上面的例子,网络服务器的每个并行用户将共享同一个数值。一些可能会一开始就重置数值,而其他则尝试去猜测它。简而言之,你清楚地了解了线程的关键问题。

我们必须持久化数据到同一请求,即使运行 PHP 多进程模型会利用线程,也必须让它绑定到当前请求中。

使用 TSRM 宏来保护全局空间

PHP 设计了可以帮助扩展和内核开发人员处理全局请求的层。该层称为TSRM (线程安全资源管理) ,并且作为一组宏公开,你必须在任何需要访问请求绑定全局(读和写)的时候使用该宏。

在多进程模型使用流程的情况下,在后台,这些宏将解析为类似我们上面显示的代码。如我们所见,如果不适用线程,上面的代码是完全有效的。所以,当使用进程时,这些宏将被扩展为类似的宏。

首先你要要做的就是声明一个结构,它将是你所有全局变量的根:

  1. ZEND_BEGIN_MODULE_GLOBALS(pib) 
  2.  
  3.     zend_long rnd; 
  4.  
  5. ZEND_END_MODULE_GLOBALS(pib) 
  6.  
  7. /* 解析为 : 
  8.  
  9. * 
  10.  
  11. * typedef struct _zend_pib_globals { 
  12.  
  13. *    zend_long rnd; 
  14.  
  15. * } zend_pib_globals; 
  16.  
  17. */ 

然后,创建一个这样的全局变量:

ZEND_DECLARE_MODULE_GLOBALS(pib)

/* 解析为 zend_pib_globals pib_globals; */

现在,你可以使用全局宏访问器访问数据。这个宏是由框架创建的,它应该在你的 php_pib.h 头文件定义。这看起来是这样的:

  1. #ifdef ZTS 
  2.  
  3. #define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v) 
  4.  
  5. #else 
  6.  
  7. #define PIB_G(v) (pib_globals.v) 
  8.  
  9. #endif 

如你所见,如果没有启用 ZTS 模式,即编译非线程安全的 PHP 和扩展(我们称之为 NTS模式:非线程安全),宏只是解析到结构中声明的数据。因此,有以下变化:

  1. static void pib_rnd_init(void) 
  2.  
  3.  
  4.     php_random_int(0, 100, &PIB_G(rnd), 0); 
  5.  
  6.  
  7.  
  8.  
  9. PHP_FUNCTION(pib_guess) 
  10.  
  11.  
  12.     zend_long r; 
  13.  
  14.  
  15.  
  16.     if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { 
  17.  
  18.         return
  19.  
  20.     } 
  21.  
  22.  
  23.  
  24.     if (r == PIB_G(rnd)) { 
  25.  
  26.         pib_rnd_init(); 
  27.  
  28.         RETURN_TRUE; 
  29.  
  30.     } 
  31.  
  32.  
  33.  
  34.     if (r < PIB_G(rnd)) { 
  35.  
  36.         RETURN_STRING("more"); 
  37.  
  38.     } 
  39.  
  40.  
  41.  
  42.     RETURN_STRING("less"); 
  43.  

注意:

当使用一个进程模型,TSRM 宏解析为对 C 全局变量的访问。

当使用线程时,即当你编译 ZTS PHP,事情变得更复杂。然后,我们看到的所有宏都解析为一些完全不同的东西,在这里很难解释。基本上,当使用 ZTS 编译时,TSRM 使用 TLS(线程本地存储)执行了一项艰难的工作。

注意:

简而言之,当在 ZTS 编译时,全局变量将绑定到当前线程。而在 NTS 编译时,全局变量将绑定到当前进程上。TSRM 宏处理这项艰难的工作。你可能对运作方式感兴趣,浏览 PHP 源代码的/TSRM 目录了解更多关于 PHP 线程安全。

在扩展中使用全局钩子

有时,可能需要将全局变量初始化为一些默认值,通常为零。引擎帮助下的TSRM系统提供了一个钩子来为您的全局变量提供默认值,我们称之为GINIT。

注意:

关于 PHP 挂钩的完整信息,请参考 PHP 生命周期章节。

让我们将随机值设为零:

  1. PHP_GSHUTDOWN_FUNCTION(pib) 
  2.  
  3. { } 
  4.  
  5.  
  6.  
  7. PHP_GINIT_FUNCTION(pib) 
  8.  
  9.  
  10.     pib_globals->rnd = 0; 
  11.  
  12.  
  13.  
  14.  
  15. zend_module_entry pib_module_entry = { 
  16.  
  17.     STANDARD_MODULE_HEADER, 
  18.  
  19.     "pib"
  20.  
  21.     NULL, 
  22.  
  23.     NULL, 
  24.  
  25.     NULL, 
  26.  
  27.     NULL, 
  28.  
  29.     NULL, 
  30.  
  31.     NULL, 
  32.  
  33.     "0.1"
  34.  
  35.     PHP_MODULE_GLOBALS(pib), 
  36.  
  37.     PHP_GINIT(pib), 
  38.  
  39.     PHP_GSHUTDOWN(pib), 
  40.  
  41.     NULL, /* PRSHUTDOWN() */ 
  42.  
  43.     STANDARD_MODULE_PROPERTIES_EX 
  44.  
  45. }; 

我们选择仅显示 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()的一部分被调用。如果你需要在每次新请求时清除全局变量,则需要像在本章所示示例中所做的那样手动进行。

完整的例子

这是一个更高级的完整示例。如果玩家获胜,则将其得分(尝试次数)添加到可以从用户区获取的得分数组中。没什么难的,得分数组在请求启动时初始化,然后在玩家获胜时使用,并在当前请求结束时清除:

  1. ZEND_BEGIN_MODULE_GLOBALS(pib) 
  2.  
  3.     zend_long rnd; 
  4.  
  5.     zend_ulong cur_score; 
  6.  
  7.     zval scores; 
  8.  
  9. ZEND_END_MODULE_GLOBALS(pib) 
  10.  
  11.  
  12.  
  13. ZEND_DECLARE_MODULE_GLOBALS(pib) 
  14.  
  15.  
  16.  
  17. static void pib_rnd_init(void) 
  18.  
  19.  
  20.     /* 重置当前分数 */ 
  21.  
  22.     PIB_G(cur_score) = 0; 
  23.  
  24.     php_random_int(0, 100, &PIB_G(rnd), 0); 
  25.  
  26.  
  27.  
  28.  
  29. PHP_GINIT_FUNCTION(pib) 
  30.  
  31.  
  32.     /* ZEND_SECURE_ZERO 是 memset(0)。也可以解析为 bzero() */ 
  33.  
  34.     ZEND_SECURE_ZERO(pib_globals, sizeof(*pib_globals)); 
  35.  
  36.  
  37.  
  38.  
  39. ZEND_BEGIN_ARG_INFO_EX(arginfo_guess, 0, 0, 1) 
  40.  
  41.     ZEND_ARG_INFO(0, num) 
  42.  
  43. ZEND_END_ARG_INFO() 
  44.  
  45.  
  46.  
  47. PHP_RINIT_FUNCTION(pib) 
  48.  
  49.  
  50.     array_init(&PIB_G(scores)); 
  51.  
  52.     pib_rnd_init(); 
  53.  
  54.  
  55.  
  56.     return SUCCESS; 
  57.  
  58.  
  59.  
  60.  
  61. PHP_RSHUTDOWN_FUNCTION(pib) 
  62.  
  63.  
  64.     zval_dtor(&PIB_G(scores)); 
  65.  
  66.  
  67.  
  68.     return SUCCESS; 
  69.  
  70.  
  71.  
  72.  
  73. PHP_FUNCTION(pib_guess) 
  74.  
  75.  
  76.     zend_long r; 
  77.  
  78.  
  79.  
  80.     if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { 
  81.  
  82.         return
  83.  
  84.     } 
  85.  
  86.  
  87.  
  88.     if (r == PIB_G(rnd)) { 
  89.  
  90.         add_next_index_long(&PIB_G(scores), PIB_G(cur_score)); 
  91.  
  92.         pib_rnd_init(); 
  93.  
  94.         RETURN_TRUE; 
  95.  
  96.     } 
  97.  
  98.  
  99.  
  100.     PIB_G(cur_score)++; 
  101.  
  102.  
  103.  
  104.     if (r < PIB_G(rnd)) { 
  105.  
  106.         RETURN_STRING("more"); 
  107.  
  108.     } 
  109.  
  110.  
  111.  
  112.     RETURN_STRING("less"); 
  113.  
  114.  
  115.  
  116.  
  117. PHP_FUNCTION(pib_get_scores) 
  118.  
  119.  
  120.     if (zend_parse_parameters_none() == FAILURE) { 
  121.  
  122.         return
  123.  
  124.     } 
  125.  
  126.  
  127.  
  128.     RETVAL_ZVAL(&PIB_G(scores), 1, 0); 
  129.  
  130.  
  131.  
  132.  
  133. PHP_FUNCTION(pib_reset) 
  134.  
  135.  
  136.     if (zend_parse_parameters_none() == FAILURE) { 
  137.  
  138.         return
  139.  
  140.     } 
  141.  
  142.  
  143.  
  144.     pib_rnd_init(); 
  145.  
  146.  
  147.  
  148.  
  149. static const zend_function_entry func[] = { 
  150.  
  151.     PHP_FE(pib_reset, NULL) 
  152.  
  153.     PHP_FE(pib_get_scores, NULL) 
  154.  
  155.     PHP_FE(pib_guess, arginfo_guess) 
  156.  
  157.     PHP_FE_END 
  158.  
  159. }; 
  160.  
  161.  
  162.  
  163. zend_module_entry pib_module_entry = { 
  164.  
  165.     STANDARD_MODULE_HEADER, 
  166.  
  167.     "pib"
  168.  
  169.     func, /* 函数入口 */ 
  170.  
  171.     NULL, /* 模块初始化 */ 
  172.  
  173.     NULL, /* 模块关闭 */ 
  174.  
  175.     PHP_RINIT(pib), /* 请求初始化 */ 
  176.  
  177.     PHP_RSHUTDOWN(pib), /* 请求关闭 */ 
  178.  
  179.     NULL, /* 模块信息 */ 
  180.  
  181.     "0.1"/* 替换为扩展的版本号 */ 
  182.  
  183.     PHP_MODULE_GLOBALS(pib), 
  184.  
  185.     PHP_GINIT(pib), 
  186.  
  187.     NULL, 
  188.  
  189.     NULL, 
  190.  
  191.     STANDARD_MODULE_PROPERTIES_EX 
  192.  
  193. }; 

这里必须要注意的是,如果你希望在请求之间保持分数,PHP 不提供任何便利。而是需要一个持久的共享存储,例如文件,数据库,某些内存区域等。PHP 的设计目的不是将信息持久存储在其内部的请求,因此它不提供这么做,但它提供了实用程序来访问请求绑定的全局空间,如我们所示。

然后,很容易地在RINIT()中初始化一个数组,然后在RSHUTDOWN()中销毁它。请记住,array_init创建一个zend_array 并放入一个 zval。但这是免分配的,不要担心分配用户无法使用的数组(因此浪费分配),array_init()非常廉价 (阅读源代码)。

当我们将这样的数组返回给用户时,我们不会忘记增加其引用计数(在 RETVAL_ZVAL中),因为我们在扩展中保留了对此类数组的引用。

使用真实的全局变量

真实全局变量是非线程保护的真实C全局变量。有时可能会需要它们。但是请记住主要规则:在处理请求时,不能安全地写入此类全局变量。因此,通常在 PHP 中,我们需要此类变量并将其用作只读变量。

请记住,在 PHP 生命周期的MINIT()或MSHUTDOWN()步骤中编写真实全局变量是绝对安全的。但是不能在处理请求时给他们写入值(但可以从他们那里读取)。

因此,一个简单的示例是你想要读取环境值以对其进行处理。此外,初始化持久性的 zend_string并在之后处理某些请求时加以利用是很常见的。

这是介绍真实全局变量的修补示例,我们仅显示与先前代码的差异,而不显示完整代码:

  1. static zend_string *more, *less; 
  2.  
  3. static zend_ulong max = 100;  
  4.  
  5. static void register_persistent_string(char *str, zend_string **result) 
  6.  
  7.  
  8.     *result = zend_string_init(str, strlen(str), 1); 
  9.  
  10.     zend_string_hash_val(*result); 
  11.  
  12.  
  13.  
  14.     GC_FLAGS(*result) |= IS_INTERNED; 
  15.  
  16. } 
  17.  
  18. static void pib_rnd_init(void) 
  19.  
  20.  
  21.     /* 重置当前分数 */ 
  22.  
  23.     PIB_G(cur_score) = 0; 
  24.  
  25.     php_random_int(0, max, &PIB_G(rnd), 0); 
  26.  
  27. } 
  28.  
  29. PHP_MINIT_FUNCTION(pib) 
  30.  
  31.  
  32.     char *pib_max; 
  33.  
  34.  
  35.  
  36.     register_persistent_string("more", &more); 
  37.  
  38.     register_persistent_string("less", &less); 
  39.  
  40.  
  41.  
  42.     if (pib_max = getenv("PIB_RAND_MAX")) { 
  43.  
  44.         if (!strchr(pib_max, '-')) { 
  45.  
  46.             max = ZEND_STRTOUL(pib_max, NULL, 10); 
  47.  
  48.         } 
  49.  
  50.     } 
  51.  
  52.  
  53.  
  54.     return SUCCESS; 
  55.  
  56.  
  57.  
  58.  
  59. PHP_MSHUTDOWN_FUNCTION(pib) 
  60.  
  61.  
  62.     zend_string_release(more); 
  63.  
  64.     zend_string_release(less); 
  65.  
  66.  
  67.  
  68.     return SUCCESS; 
  69.  
  70.  
  71.  
  72.  
  73. PHP_FUNCTION(pib_guess) 
  74.  
  75.  
  76.     zend_long r; 
  77.  
  78.  
  79.  
  80.     if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &r) == FAILURE) { 
  81.  
  82.         return
  83.  
  84.     } 
  85.  
  86.  
  87.  
  88.     if (r == PIB_G(rnd)) { 
  89.  
  90.         add_next_index_long(&PIB_G(scores), PIB_G(cur_score)); 
  91.  
  92.         pib_rnd_init(); 
  93.  
  94.         RETURN_TRUE; 
  95.  
  96.     } 
  97.  
  98.  
  99.  
  100.     PIB_G(cur_score)++; 
  101.  
  102.  
  103.  
  104.     if (r < PIB_G(rnd)) { 
  105.  
  106.         RETURN_STR(more); 
  107.  
  108.     } 
  109.  
  110.  
  111.  
  112.     RETURN_STR(less); 
  113.  

在这里我们创建了两个 zend_string 变量 more 和 less。这些字符串不需要像以前一样在使用时立即创建和销毁。这些是不可变的字符串,只要保持不变,就可以分配一次并在需要的任何时间重复使用(即只读)。我们在zend_string_init()中使用持久分配,在MINIT()中初始化这两个字符串,我们现在预先计算其哈希值(而不是先执行第一个请求),并且我们告诉 zval 垃圾收集器,这些字符串已被扣留,因此它将永远不会尝试销毁它们(但是,如果将它们用作写操作(例如连接)的一部分,则可能需要复制它们)。显然我们不会忘记在MSHUTDOWN()中销毁这些字符串。

然后在MINIT()中我们探查一个PIB_RAND_MAX环境,并将其用作随机数选择的最大范围值。由于我们使用无符号整数,并且我们知道strtoull()不会抱怨负数(因此将整数范围包裹为符号不匹配),我们只是避免使用负数(经典的libc解决方法)。

Tags: php管理全局状态

分享到: