设为首页 加入收藏

TOP

PHP 源码学习之线程安全(四)
2017-10-10 11:57:32 】 浏览:10126
Tags:PHP 源码 习之 线程 安全
ble 中,当一个新的资源被分配时,就会创建一个 tsrm_resource_type。 所有 tsrm_resource_type 以数组的方式组成 tsrm_resource_table,其下标就是这个资源的 ID。 其实我们可以将 tsrm_resource_table 看做一个 HASH 表,key 是资源 ID,value 是 tsrm_resource_type 结构(任何一个数组都可以看作一个 HASH 表,如果数组的key 值有意义的话)。

在分配了资源 ID 后,PHP 内核会接着遍历所有线程为每一个线程的 tsrm_tls_entry 分配这个线程全局变量需要的内存空间。 这里每个线程全局变量的大小在各自的调用处指定(也就是全局变量结构体的大小)。最后对地址存放的全局变量进行初始化。为此我画了一张图予以说明

图8.2 PHP 线程安全示意图

上图中还有一个困惑的地方,tsrm_tls_table 的元素是如何添加的,链表是如何实现的。我们把这个问题先留着,后面会讨论。

每一次的 ts_allocate_id 调用,PHP 内核都会遍历所有线程并为每一个线程分配相应资源, 如果这个操作是在PHP生命周期的请求处理阶段进行,岂不是会重复调用?

PHP 考虑了这种情况,ts_allocate_id 的调用在模块初始化时就调用了。

TSRM 启动后,在模块初始化过程中会遍历每个扩展的模块初始化方法, 扩展的全局变量在扩展的实现代码开头声明,在 MINIT 方法中初始化。 其在初始化时会知会 TSRM 申请的全局变量以及大小,这里所谓的知会操作其实就是前面所说的 ts_allocate_id 函数。 TSRM 在内存池中分配并注册,然后将资源ID返回给扩展。

全局变量的使用

以标准的数组扩展为例,首先会声明当前扩展的全局变量。

ZEND_DECLARE_MODULE_GLOBALS(array)

然后在模块初始化时会调用全局变量初始化宏初始化 array,比如分配内存空间操作。

static void php_array_init_globals(zend_array_globals *array_globals)
{
    memset(array_globals, 0, sizeof(zend_array_globals));
}

/* code... */

PHP_MINIT_FUNCTION(array) /* {{{ */
{
    ZEND_INIT_MODULE_GLOBALS(array, php_array_init_globals, NULL);
    /* code... */
}

这里的声明和初始化操作都是区分ZTS和非ZTS。

#ifdef ZTS

#define ZEND_DECLARE_MODULE_GLOBALS(module_name)                            \
    ts_rsrc_id module_name##_globals_id;

#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)   \
    ts_allocate_id(&module_name##_globals_id, sizeof(zend_##module_name##_globals), (ts_allocate_ctor) globals_ctor, (ts_allocate_dtor) globals_dtor);

#else

#define ZEND_DECLARE_MODULE_GLOBALS(module_name)                            \
    zend_##module_name##_globals module_name##_globals;

#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)   \
    globals_ctor(&module_name##_globals);

#endif

对于非ZTS的情况,直接声明变量,初始化变量;对于ZTS情况,PHP内核会添加TSRM,不再是声明全局变量,而是用ts_rsrc_id代替,初始化时也不再是初始化变量,而是调用ts_allocate_id函数在多线程环境中给当前这个模块申请一个全局变量并返回资源ID。其中,资源ID变量名由模块名加global_id组成。

如果要调用当前扩展的全局变量,则使用:ARRAYG(v),这个宏的定义:

#ifdef ZTS
#define ARRAYG(v) TSRMG(array_globals_id, zend_array_globals *, v)
#else
#define ARRAYG(v) (array_globals.v)
#endif

如果是非ZTS则直接调用全局变量的属性字段,如果是ZTS,则需要通过TSRMG获取变量。

TSRMG的定义:

#define TSRMG(id, type, element) (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)

去掉这一堆括号,TSRMG宏的意思就是从tsrm_ls中按资源ID获取全局变量,并返回对应变量的属性字段。

那么现在的问题是这个 tsrm_ls 从哪里来的?

tsrm_ls 的初始化

tsrm_ls 通过 ts_resource(0) 初始化。展开实际最后调用的是 ts_resource_ex(0,NULL) 。下面将 ts_resource_ex 一些宏展开,线程以 pthread 为例。

#define THREAD_HASH_OF(thr,ts)  (unsigned long)thr%(unsigned long)ts

static MUTEX_T tsmm_mutex;

void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
{
    THREAD_T thread_id;
    int hash_value;
    tsrm_tls_entry *thread_resources;

    // tsrm_tls_table 在 tsrm_startup 已初始化完毕
    if(tsrm_tls_table) {
        // 初始化时 th_id = NULL;
        if (!th_id) {

            //第一次为空 还未执行过 pthread_setspecific 所以 thread_resources 指针为空
            thread_resources = pthread_getspecific(tls_key);

            if(thread_resources){
                TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);
            }

            thread_id = pthread_self();
        } else {
            thread_id = *th_id;
        }
    }
    // 上锁
    pthread_mutex_
首页 上一页 1 2 3 4 5 下一页 尾页 4/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇PHP mysqli_free_result()与mysql.. 下一篇WNMP集成环境下配置thinkPHP

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目