PHP 管理全局的方法

胖墨姑娘_4312

胖墨姑娘_4312

2020-09-03

2353人浏览

转载

【相关学习推荐: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 <p>如你所见,这个扩展在请求开始时挑选一个随机整型数,之后通过<code>pib_guess()</code>可以尝试猜到这个数组。一旦猜到,该数字将重置。如果用户想要手动重置数字,它也可以自己手动调用<code>pib_reset()</code> 去重置数值。<br>该随机数作为<em>一个 C 全局变量</em>实现。如果 PHP 在进程中作为多进程模型的一部分使用不再是个问题,如果之后使用线程,<strong>这是不行的</strong>。</p><blockquote>注意作为提醒,你无需掌握将要使用哪种多进程模型。当你设计扩展时,你必须为这两种模型做好准备。</blockquote><p>当使用线程,会针对服务器中的每个线程共享一个 C 全局变量。例如我们上面的例子,网络服务器的每个并行用户将共享同一个数值。一些可能会一开始就重置数值,而其他则尝试去猜测它。简而言之,你清楚地了解了线程的关键问题。</p><p>我们必须持久化数据到同一请求,即使运行 PHP 多进程模型会利用线程,也必须让它<strong>绑定</strong>到当前请求中。</p><h3>使用 TSRM 宏来保护全局空间</h3><p>PHP 设计了可以帮助扩展和内核开发人员处理全局请求的层。该层称为<strong>TSRM</strong> (线程安全资源管理) ,并且作为一组宏公开,你必须在任何需要访问请求绑定全局(读和写)的时候使用该宏。</p><p>在多进程模型使用流程的情况下,在后台,这些宏将解析为类似我们上面显示的代码。如我们所见,如果不适用线程,上面的代码是完全有效的。所以,当使用进程时,这些宏将被扩展为类似的宏。</p><p>首先你要要做的就是声明一个结构,它将是你所有全局变量的根:</p><pre class="brush:php;toolbar:false;">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 <blockquote>
<p>注意</p>
<p>当使用一个进程模型,<em>TSRM</em> 宏解析为对 C 全局变量的访问。</p>
</blockquote><p>当使用线程时,即当你编译 ZTS PHP,事情变得更复杂。然后,我们看到的所有宏都解析为一些完全不同的东西,在这里很难解释。基本上,当使用 ZTS 编译时,<em>TSRM</em> 使用 TLS(线程本地存储)执行了一项艰难的工作。</p><blockquote>
<p>注意</p>
<p>简而言之,当在 ZTS 编译时,全局变量将绑定到当前线程。而在 NTS 编译时,全局变量将绑定到当前进程上。TSRM 宏处理这项艰难的工作。你可能对运作方式感兴趣,浏览 PHP 源代码的/TSRM 目录了解更多关于 PHP 线程安全。</p>
</blockquote><h3>在扩展中使用全局钩子</h3><p>有时,可能需要将全局变量初始化为一些默认值,通常为零。引擎帮助下的TSRM系统提供了一个钩子来为您的全局变量提供默认值,我们称之为<strong>GINIT</strong>。</p><blockquote>
<p>注意</p>
<p>关于 PHP 挂钩的完整信息,请参考 PHP 生命周期章节。</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站"><img
														src="https://img.php.cn/upload/skill/000/000/081/179040786932301.jpg" alt="btpanel phpsite 宝塔面板PHP网站" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站" class="overflowclass">btpanel phpsite 宝塔面板PHP网站</a>
													<p class="overflowclass">宝塔面板 PHP 网站管理:站点创建、删除、启停、PHP 版本切换、域名管理、SSL证书管理、伪静态管理、数据库管理</p>
												</div>
												<a rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
</blockquote><p>让我们将随机值设为零:</p><pre class="brush:php;toolbar:false;">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 <p>这里必须要注意的是,如果你希望在请求之间保持分数,PHP 不提供任何便利。而是需要一个持久的共享存储,例如文件,数据库,某些内存区域等。PHP 的设计目的不是将信息持久存储在其内部的请求,因此它不提供这么做,但它提供了实用程序来访问请求绑定的全局空间,如我们所示。</p><p>然后,很容易地在<code>RINIT()</code>中初始化一个数组,然后在<code>RSHUTDOWN()</code>中销毁它。请记住,<code>array_init</code>创建一个zend_array 并放入一个 zval。但这是免分配的,不要担心分配用户无法使用的数组(因此浪费分配),<code>array_init()</code>非常廉价 (阅读源代码)。</p><p>当我们将这样的数组返回给用户时,我们不会忘记增加其引用计数(在 <code>RETVAL_ZVAL</code>中),因为我们在扩展中保留了对此类数组的引用。</p><h2>使用真实的全局变量</h2><p>真实全局变量是非线程保护的真实C全局变量。有时可能会需要它们。但是请记住主要规则:在处理请求时,不能安全地写入此类全局变量。因此,通常在 PHP 中,我们需要此类变量并将其用作只读变量。</p><p>请记住,在 PHP 生命周期的<code>MINIT()</code>或<code>MSHUTDOWN()</code>步骤中编写真实全局变量是绝对安全的。但是不能在处理请求时给他们写入值(但可以从他们那里读取)。</p><p>因此,一个简单的示例是你想要读取环境值以对其进行处理。此外,初始化持久性的 zend_string并在之后处理某些请求时加以利用是很常见的。</p><p>这是介绍真实全局变量的修补示例,我们仅显示与先前代码的差异,而不显示完整代码:</p><pre class="brush:php;toolbar:false;">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 <p>在这里我们创建了两个 zend_string 变量 <code>more</code> 和 <code>less</code>。这些字符串不需要像以前一样在使用时立即创建和销毁。这些是不可变的字符串,只要保持不变,就可以分配一次并在需要的任何时间重复使用(即只读)。我们在<code>zend_string_init()</code>中使用持久分配,在<code>MINIT()</code>中初始化这两个字符串,我们现在预先计算其哈希值(而不是先执行第一个请求),并且我们告诉 zval 垃圾收集器,这些字符串已被扣留,因此它将永远不会尝试销毁它们(但是,如果将它们用作写操作(例如连接)的一部分,则可能需要复制它们)。显然我们不会忘记在<code>MSHUTDOWN()</code>中销毁这些字符串。</p><p>然后在<code>MINIT()</code>中我们探查一个<code>PIB_RAND_MAX</code>环境,并将其用作随机数选择的最大范围值。由于我们使用无符号整数,并且我们知道<code>strtoull()</code>不会抱怨负数(因此将整数范围包裹为符号不匹配),我们只是避免使用负数(经典的libc解决方法)。</p><p style="position: absolute!important;height: 1px;width: 1px;overflow: hidden;clip: rect(1px,1px,1px,1px);"><br></p><blockquote><p>想了解更多编程学习,敬请关注<a href="https://www.php.cn/" target="_blank">php培训</a>栏目!<br></p></blockquote>

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

60

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

40

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

120

10

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

140

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

120

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

80

15

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

2026.10.22

40

10

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

2026.10.22

80

25

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

2026.10.22

40

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习