>  기사  >  백엔드 개발  >  PHP는 글로벌 상태를 관리합니다

PHP는 글로벌 상태를 관리합니다

coldplay.xixi
coldplay.xixi앞으로
2020-07-28 16:43:101712검색

전역 상태 관리 命 명령 언어에는 항상 전역 공간이 있습니다. 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();
}
보시다시피 이 확장은 요청이 시작될 때 선택됩니다. 임의의 정수를 입력한 다음

를 사용하여 값을 재설정합니다.

난수는

C 전역 변수

로 구현됩니다. PHP가 다중 프로세스 모델의 일부로 프로세스 내에서 사용되거나 나중에 스레드가 사용되는 경우에는 더 이상 문제가 되지 않습니다.

이것은 안 됩니다pib_guess()可以尝试猜到这个数组。一旦猜到,该数字将重置。如果用户想要手动重置数字,它也可以自己手动调用pib_reset().
참고 참고로 어떤 다중 프로세스 모델을 사용할지 알 필요는 없습니다. 확장을 설계할 때는 두 모델을 모두 준비해야 합니다.

스레드를 사용하는 경우 서버의 각 스레드마다 C 전역 변수가 공유됩니다. 예를 들어 위의 예에서 네트워크 서버의 각 동시 사용자는 동일한 값을 공유합니다. 일부는 처음에 값을 재설정할 수도 있고 다른 일부는 추측을 시도할 수도 있습니다. 즉, 스레드의 핵심 문제를 명확하게 이해했습니다.

동일한 요청에 대해 데이터를 유지해야 합니다. PHP 다중 프로세스 모델을 실행하여 스레드를 활용하더라도 데이터는 현재 요청에

바인딩
되어야 합니다.

TSRM 매크로를 사용하여 전역 공간 보호

PHP는 확장 프로그램 및 커널 개발자가 전역 요청을 처리하는 데 도움이 되는 계층으로 설계되었습니다. 이 레이어는 TSRM

(Thread-Safe Resource Management)라고 하며 요청 바인딩 전역(읽기 및 쓰기)에 액세스해야 할 때마다 사용해야 하는 매크로 세트로 노출됩니다.

프로세스를 사용하는 다중 프로세스 모델의 경우, 뒤에서 이러한 매크로는 위에서 보여준 것과 유사한 코드로 구문 분석됩니다. 보시다시피 위 코드는 스레딩을 사용하지 않으면 완전히 유효합니다. 따라서 프로세스를 사용할 때 이러한 매크로는 유사한 매크로로 확장됩니다.

가장 먼저 해야 할 일은 모든 전역 변수의 루트가 될 구조를 선언하는 것입니다.

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");
}

Note프로세스 모델을 사용할 때

TSRM
매크로는 C 전역 변수에 대한 액세스를 확인합니다.

스레드를 사용하는 경우, 즉 ZTS PHP를 컴파일하는 경우 상황이 더 복잡해집니다. 그런 다음 우리가 보는 모든 매크로는 완전히 다른 것으로 해결되는데, 이는 여기서 설명하기 어렵습니다. 기본적으로 TSRM은 ZTS로 컴파일할 때 TLS(Thread Local Storage)를 사용하여 어려운 작업을 수행합니다.

NOTE즉, ZTS에서 컴파일할 때 전역 변수는 현재 스레드에 바인딩됩니다. NTS로 컴파일할 때 전역 변수는 현재 프로세스에 바인딩됩니다. TSRM 매크로는 어려운 작업을 처리합니다. 이것이 어떻게 작동하는지 관심이 있을 수 있으며 PHP 스레드 안전에 대해 자세히 알아보려면 PHP 소스 코드의 /TSRM 디렉터리를 검색하세요.

在扩展中使用全局钩子

有时,可能需要将全局变量初始化为一些默认值,通常为零。引擎帮助下的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()来确定全局变量的大小,然后是我们的 GINITGSHUTDOWN钩子。然后我们使用了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, &#39;-&#39;)) {
            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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제