ホームページ  >  記事  >  バックエンド開発  >  PHP はグローバルステータスを管理します

PHP はグローバルステータスを管理します

coldplay.xixi
coldplay.xixi転載
2020-07-28 16:43:101771ブラウズ

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 がマルチプロセス モデルの一部としてインプロセスで使用される場合は問題になりませんが、後でスレッドが使用される場合は、これは問題ありません

NOTE

注意事項として、どのマルチプロセス モデルを使用するかを知る必要はありません。拡張機能を設計するときは、両方のモデルに対応できるように準備する必要があります。

スレッドを使用する場合、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");
}

Note

プロセス モデルを使用する場合、TSRM マクロは C グローバル変数へのアクセスを解決します。

スレッドを使用する場合、つまり ZTS PHP をコンパイルする場合、状況はさらに複雑になります。次に、私たちが目にするすべてのマクロは、まったく異なるものに解決されますが、これをここで説明するのは困難です。基本的に、TSRM は、ZTS でコンパイルされると、TLS (スレッド ローカル ストレージ) を使用して難しい仕事を行います。

NOTE

つまり、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()来确定全局变量的大小,然后是我们的 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。