【问题标题】:php-src - ZEND_TSRMLS_CACHE_UPDATE - what is it and when it is needed?php-src - ZEND_TSRMLS_CACHE_UPDATE - 它是什么以及何时需要?
【发布时间】:2020-03-13 16:50:34
【问题描述】:

php/extskeleton file 中的ZEND_TSRMLS_CACHE_UPDATE 指令有什么作用?

何时以及为什么需要它?

当另一个初始化。添加了PHP_MINIT_FUNCTION(%EXTNAME%) 之类的函数,是否也需要将ZEND_TSRMLS_CACHE_UPDATE 指令作为该函数中的第一条语句?

【问题讨论】:

  • 你看过this的描述吗?宏 ZEND_TSRMLS_CACHE_UPDATE 扩展为 WINAPI 函数 TlsGetValue()。还有一个description and example的用法。
  • 补充:问题与Windows有关,但在POSIX系统上宏扩展为POSIX-Threads函数pthread_getspecific()。如果它更清晰。

标签: php c++ c windows thread-safety


【解决方案1】:

首先你的问题不清楚,原因有二:

  1. 您必须提到您正在尝试理解 ZTS,即 Zend 使用 PHP 实现线程安全

  2. 您的文件编译或了解您的系统是什么 要求,即您使用的操作系统以及您使用的线程类型。

第二件事你还没明白MINIT和RINIT的区别先让我们了解一下两者的区别

MINIT 与 RINIT

您必须了解 C 编程中的全局变量。假设你对 PHP 的扩展编译有一定的了解。在 PHP 中,全局变量分为两种类型:

1。 True Globals / MINI

真正的全局变量是传统的 C 全局变量,因为它们在设计上还不错,但在线程并发运行环境中无法保护。 PHP 允许他们在 PHP 处理请求时读取。真正的全局变量可以在 Threads 环境内部或外部进行更改。让我们看一个例子来进一步解释它:

static int variable; /* true global */

PHP_MINIT(my_ext) /* PHP Module initialization */
{
        if (something()) {
                variable = 3; /* writing to a true global */
        }
}

上面的代码解释了每个 PHP 扩展的样子。所谓的 MINIT 钩子是关于 PHP 扩展初始化的。在这一步骤中,PHP 正在启动,然后可以安全地写入或读取全局变量,就像示例中所做的那样。

2。线程全局/RINI

由于创建线程是为了同时处理多个请求,因此在该线程中保持每个变量值的完整性非常重要,因此值只能由该线程读取或写入。因此,线程请求的初始化是通过 RINIT 完成的。现在同样的例子会是这样的:

PHP_RINIT(my_ext) /* PHP Request initialization */
{
        if (something()) {
                MYEXT_G(variable) = 3; /* writing to a thread global */
        }
}

只有 1 个区别,即使用 MYEXT_G 宏,我将在此答案后面解释。

为了更好地理解事物,您可能还需要了解 TSRM

TSRM

ZTS 是为使用线程安全资源管理器层而设计的,我们称之为 TSRM 层。这只是一些普通的 C 代码,对于线程仅是理解事物所需的一些解释。它启用了一些低级线程库:

  1. Gnu 便携式线程,
  2. Posix 线程,
  3. 状态线程,
  4. Win32 线程或
  5. BeThreads。

TSRM 引导

在 TSRM 启动 PHP 调用 tsrm_startup()。由于 PHP 启动并没有如何构建安全保护所需的线程或资源。它将为每个创建的线程准备一个检查表。这个启动步骤也很重要,因为我们在这里创建了 TLS 密钥和我们需要同步的 TLS 互斥锁。

static pthread_key_t tls_key;

TSRM_API int tsrm_startup(int expected_threads, 
                          int expected_resources, int debug_level, char *debug_filename)
{
        pthread_key_create( &tls_key, 0 ); /* Create the key */
        ....
        ....
}

#define MUTEX_T pthread_mutex_t *

TSRM_API MUTEX_T tsrm_mutex_alloc(void)
{
        MUTEX_T mutexp;
        mutexp = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
        pthread_mutex_init(mutexp,NULL);
        return mutexp;
}

TSRM 资源

是时候添加新的资源线程了。内存区域有大小,需要一些初始化(构造函数)和反初始化(析构函数)。该内存区域(称为 TSRM 资源)将由 TSRM 层赋予唯一的资源 ID。调用方应保存 ID,因为它需要从 TSRM 中返回受保护的内存区域以读取或写入变量值。

TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, 
        size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
{
     ....
     //For allocation of memory saving constructor and destruction function 
     // Saving Size of required memory allocated
     // Resources ID Returning back
}

按要求启动

在每个新请求的最开始,都会调用 ts_resource_ex() 函数。该函数读取当前线程 id 并尝试获取分配给该线程的资源,也就是当前线程全局专用的内存区域。

ZEND_TSRMLS_CACHE_UPDATE

现在是您对 ZEND_TSRMLS_CACHE_UPDATE 问题的回答。

/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(%EXTNAME%)
{
#if defined(ZTS) && defined(COMPILE_DL_%EXTNAMECAPS%)
    ZEND_TSRMLS_CACHE_UPDATE();
#endif

    return SUCCESS;
}
/* }}} */

这里是宏扩展:

#define ZEND_TSRMLS_CACHE_UPDATE() _tsrm_ls_cache = tsrm_get_ls_cache();

对于 pthread 实现:

#define tsrm_get_ls_cache pthread_getspecific(tls_key)

最后,您应该更好地了解现在如何在扩展中使用宏访问全局变量:

#ifdef ZTS
#define MYEXT_G(v) (((MYEXT_globals *) (*((void ***) _tsrm_ls_cache))[((MYEXT_globals_id)-1)])->(v))

使用 MYEXT_G() 宏访问全局变量,当使用线程环境时,它将扩展以使用此扩展的 id 探测 _tsrm_ls_cache 区域:MYEXT_globals_id

【讨论】:

  • 感谢您的解释。因此,如果我不为每个模块设置任何全局变量,是否可以安全地省略 ZEND_TSRMLS_CACHE_UPDATE 调用? pthread_getspecific 或等效项对 Windows 的性能影响是什么?当 PHP 执行请求初始化时,ZEND_TSRMLS_CACHE_UPDATE 指令没有内联 PHP_RINIT_FUNCTION(%EXTNAME%) 宏或类似代码是否有任何原因?
  • 这取决于你的实现。我已经给了你关于如何完成事情的想法。不知道您正在编译或阅读哪个实现。我个人的观点是在你 100% 确定影响之前不要放弃。
猜你喜欢
  • 1970-01-01
  • 2014-06-01
  • 1970-01-01
  • 2015-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多