Heim >Backend-Entwicklung >PHP-Tutorial >Globale PHP-Verwaltungsmethode

Globale PHP-Verwaltungsmethode

coldplay.xixi
coldplay.xixinach vorne
2020-09-03 16:27:382200Durchsuche

Globale PHP-Verwaltungsmethode

【Verwandte Lernempfehlung: php-Programmierung(Video)】

Globalen Zustand verwalten

In imperativen Sprachen wird immer etwas globaler Raum benötigt. Bei der Programmierung von PHP oder Erweiterungen werden wir eine klare Unterscheidung zwischen sogenannten anforderungsgebundenen globalen Variablen und echten globalen Variablen treffen.

Anforderungsglobale Variablen sind globale Variablen, die während der Anforderungsverarbeitung Informationen übertragen und speichern müssen. Ein einfaches Beispiel ist, wenn Sie den Benutzer auffordern, einen Wert in einem Funktionsparameter anzugeben, und diesen in anderen Funktionen verwenden möchten. Abgesehen davon, dass diese Informationen über mehrere PHP-Funktionsaufrufe hinweg „ihren Wert behalten“, behalten sie nur den Wert für die aktuelle Anfrage. Die nächste Anfrage, die kommt, sollte nichts wissen. PHP bietet einen Mechanismus zum Verwalten globaler Anforderungsvariablen unabhängig vom gewählten Multiprocessing-Modell, auf den wir später in diesem Kapitel ausführlich eingehen werden.

Echte globale Variablen sind Informationen, die über alle Anfragen hinweg beibehalten werden. Diese Informationen sind normalerweise schreibgeschützt. Wenn Sie im Rahmen der Anforderungsbearbeitung in eine solche globale Variable schreiben müssen, kann PHP Ihnen nicht helfen. Wenn Sie Threads als Multiprocessing-Modell verwenden, müssen Sie die Speichersperre selbst implementieren. Wenn Sie Prozesse als Multiprocessing-Modell verwenden, müssen Sie Ihren eigenen IPC (Inter-Process-Communication) verwenden. Dies sollte jedoch bei der PHP-Erweiterungsprogrammierung nicht passieren.

Anforderungsglobale Variablen verwalten

Hier ist ein einfaches Erweiterungsbeispiel mit Anforderungsglobalen:

/* 真正的 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();
}

Wie Sie sehen können, wählt diese Erweiterung eine zufällige Ganzzahl am Anfang der Anforderung aus und setzt den Wert dann über pib_guess()可以尝试猜到这个数组。一旦猜到,该数字将重置。如果用户想要手动重置数字,它也可以自己手动调用pib_reset() zurück.
Die Zufallszahl ist als eine globale C-Variable implementiert. Es ist kein Problem mehr, wenn PHP prozessintern als Teil eines Multiprozessmodells verwendet wird, wenn Threads später verwendet werden. Das ist ein Nein-Nein.

HINWEIS

Zur Erinnerung: Sie müssen nicht wissen, welches Multiprozessmodell Sie verwenden werden. Wenn Sie eine Erweiterung entwerfen, müssen Sie auf beide Modelle vorbereitet sein.

Bei der Verwendung von Threads wird eine globale C-Variable für jeden Thread auf dem Server gemeinsam genutzt. In unserem Beispiel oben hat beispielsweise jeder gleichzeitige Benutzer des Netzwerkservers denselben Wert. Einige setzen den Wert möglicherweise zunächst zurück, während andere versuchen, ihn zu erraten. Kurz gesagt, Sie verstehen klar, worum es bei Threads geht.

Wir müssen Daten an die gleiche Anfrage binden, auch wenn die Ausführung des PHP-Multiprozessmodells Threads verwendet, müssen sie an die aktuelle Anfrage gebunden sein.

Verwenden Sie das TSRM-Makro, um den globalen Speicherplatz zu schützen.

PHP ist mit einer Ebene ausgestattet, die Erweiterungs- und Kernel-Entwicklern bei der Bearbeitung globaler Anforderungen hilft. Diese Ebene heißt TSRM (Thread-Safe Resource Management) und wird als eine Reihe von Makros bereitgestellt, die Sie jedes Mal verwenden müssen, wenn Sie auf die anforderungsgebundenen Globals zugreifen müssen (Lesen und Schreiben).

Im Fall eines Multiprozessmodells, das Prozesse verwendet, werden diese Makros hinter den Kulissen in Code analysiert, der dem oben gezeigten ähnelt. Wie wir sehen können, ist der obige Code vollständig gültig, wenn kein Threading verwendet wird. Bei der Verwendung von Prozessen werden diese Makros also zu ähnlichen Makros erweitert.

Als erstes müssen Sie eine Struktur deklarieren, die die Wurzel aller Ihrer globalen Variablen sein wird:

ZEND_BEGIN_MODULE_GLOBALS(pib)
    zend_long rnd;
ZEND_END_MODULE_GLOBALS(pib)

/* 解析为 :
*
* typedef struct _zend_pib_globals {
*    zend_long rnd;
* } zend_pib_globals;
*/

Dann erstellen Sie eine globale Variable wie diese:

ZEND_DECLARE_MODULE_GLOBALS(pib)

/* 解析为 zend_pib_globals pib_globals; */

Jetzt können Sie mit dem globalen Makro auf die Daten zugreifen Zugriffsberechtigter. Dieses Makro wird vom Framework erstellt und sollte in Ihrer Header-Datei php_pib.h definiert werden. Das sieht so aus:

#ifdef ZTS
#define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v)
#else
#define PIB_G(v) (pib_globals.v)
#endif

Wie Sie sehen können, werden die Makros einfach aufgelöst, wenn der ZTS-Modus nicht aktiviert ist, d zu den in deklarierten Strukturdaten. Daher gibt es folgende Änderungen:

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

Hinweis

Bei Verwendung eines Prozessmodells wird das

TSRM

-Makro aufgelöst, um auf globale C-Variablen zuzugreifen.

Bei der Verwendung von Threads, also beim Kompilieren von ZTS PHP, wird es komplizierter. Dann werden alle Makros, die wir sehen, zu etwas völlig anderem aufgelöst, was hier schwer zu erklären ist. Grundsätzlich leistet
TSRM

bei der Kompilierung mit ZTS schwere Arbeit bei der Verwendung von TLS (Thread Local Storage).

HINWEIS

Kurz gesagt, beim Kompilieren in ZTS werden globale Variablen an den aktuellen Thread gebunden. Beim Kompilieren mit NTS werden globale Variablen an den aktuellen Prozess gebunden. Das TSRM-Makro übernimmt die harte Arbeit. Vielleicht interessiert Sie, wie das funktioniert, und durchsuchen Sie das /TSRM-Verzeichnis des PHP-Quellcodes, um mehr über die PHP-Thread-Sicherheit zu erfahren.

Globale Hooks in Erweiterungen verwenden

Manchmal kann es notwendig sein, globale Variablen auf einen Standardwert, normalerweise Null, zu initialisieren. Das TSRM-System stellt mit Hilfe der Engine einen Hook zur Bereitstellung von Standardwerten für Ihre globalen Variablen bereit, wir nennen ihn

GINIT

.

Achtung

关于 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, '-')) {
            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解决方法)。


Wenn Sie mehr über das Programmieren erfahren möchten, achten Sie bitte auf die Rubrik PHP-Schulung!

Das obige ist der detaillierte Inhalt vonGlobale PHP-Verwaltungsmethode. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen