获取资源

6.2.1.3 获取资源

资源的id在注册后需要保存下来,根据id可以通过ts_resource()获取到对应资源的值,比如EG,这里暂不考虑EG宏展开的结果,只分析最底层的根据资源id获取资源的操作。

zend_executor_globals *executor_globals;
executor_globals = ts_resource(executor_globals_id);

这样获取的executor_globals值就是各线程分离的了,对它的操作将不会再影响其它线程。根据资源id获取当前线程资源的过程:首先是根据线程id哈希得到当前线程的tsrm_tls_entry在tsrm_tls_table哪个槽中,然后开始遍历比较id,直到找到当前线程的tsrm_tls_entry,这个查找过程是需要加锁的,最后根据资源id从storage中对应位置取出资源的地址,这个时候如果发现当前线程还没有创建此资源则会从resource_types_table根据资源id取出资源注册时的大小、初始化函数,然后分配内存、调用初始化函数进行初始化并插入所属线程的storage中。

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

    //step 1:获取线程id
    if (!th_id) {
        //获取当前线程通过specific data保存的tsrm_tls_entry,暂时忽略
        thread_resources = tsrm_tls_get();
        if(thread_resources){
            //找到线程的tsrm_tls_entry了
            TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count); //直接返回
        }
        //pthread_self(),当前线程id
        thread_id = tsrm_thread_id();
    }else{
        thread_id = *th_id;
    }

    //step 2:查找线程tsrm_tls_entry
    tsrm_mutex_lock(tsmm_mutex); //加锁

    //实际就是thread_id % tsrm_tls_table_size
    hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);
    //链表头部
    thread_resources = tsrm_tls_table[hash_value];
    if (!thread_resources) {
        //当前线程第一次使用资源还未分配:先分配tsrm_tls_entry
        allocate_new_resource(&tsrm_tls_table[hash_value], thread_id);
        //分配完再次调用,这时候将走到下面的分支
        return ts_resource_ex(id, &thread_id);
    }else{
        //遍历查找当前线程的tsrm_tls_entry
        do {
            //找到了
            if (thread_resources->thread_id == thread_id) {
                break;
            }
            if (thread_resources->next) {
                thread_resources = thread_resources->next;
            } else {
                //遍历到最后也没找到,与上面的一致,先分配再查找
                allocate_new_resource(&thread_resources->next, thread_id);
                return ts_resource_ex(id, &thread_id);
            }
        } while (thread_resources);
    }
    //解锁
    tsrm_mutex_unlock(tsmm_mutex);

    //step 3:返回资源
    TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);
}

首先是获取线程id,如果没有传的话就是当前线程,然后在tsrm_tls_table中查找当前线程的tsrm_tls_entry,不存在则表示当前线程第一次使用资源,则需要调用allocate_new_resource()为当前线程分配tsrm_tls_entry,并插入tsrm_tls_table,这个过程还会为当前已注册的所有资源分配内存:

static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
{
    (*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry));
    (*thread_resources_ptr)->storage = NULL;
    //根据已注册资源数分配storage数组大小,注意这里并不是分配为各资源分配空间
    if (id_count > 0) {
        (*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count);
    }
    (*thread_resources_ptr)->count = id_count;
    (*thread_resources_ptr)->thread_id = thread_id;

    //将当前线程的tsrm_tls_entry保存到线程本地存储(Thread Local Storage, TLS)
    tsrm_tls_set(*thread_resources_ptr);

    //为全部资源分配空间
    for (i=0; istorage[i] = (void *) malloc(resource_types_table[i].size);
        ...
    }
    ...
}

这里还用到了一个多线程中经常用到的一个东西:线程本地存储(Thread Local Storage, TLS),在创建完当前线程的tsrm_tls_entry后会把这个值保存到当前线程的TLS中(即:tsrm_tls_set(*thread_resources_ptr)操作),这样在ts_resource()中就可以通过tsrm_tls_get()直接取到了,节省加锁检索的时间。

> 线程本地存储(Thread Local Storage, TLS): 我们知道在一个进程中,所有线程是共享同一个地址空间的。所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线程对其进行了修改,也就会影响到其他所有的线程。不过我们可能并不希望这样,所以更多的推荐用基于堆栈的自动变量或函数参数来访问数据,因为基于堆栈的变量总是和特定的线程相联系的。TLS在各平台下实现方式不同,主要分为两类:静态TLS、动态TLS,pthread中pthread_setspecific()、pthread_getspecific()的实现就可以认为是动态TLS的实现。

比如tsrm_tls_table_size初始化时设置为了2,当前有2个thread:thread 1、thread 2,假如注册了CG、EG两个资源,则存储结构如下图:

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3