这里写自定义目录标题
前言
昨天经过一天的学习,终于对PHP的TSRM有了初步了解,及时整理一下,加深理解。
主要参考文章
关于PHP TSRM
这是啥
Thread Safe Resource Manager 线程安全的资源管理器
用来干嘛的
一些Global变量,在多线程的模型下,不同的线程会共享同一个进程的内存空间,也就是多个线程会共用一个全局变量。
也就是说『不是线程安全的』。
为了解决这个问题,咱们就引入了TSRM,给每一个新创建的线程,都整一份全局变量的副本。就是这么简单的核心思想。请记住我们的这个目标,后面的事情都是为了实现这个目标而已
(原谅我看了一天晕头转向没抓住重点)
怎么实现的
先直接看图:
首先,看黄色注释。
这是两个线程、三个模块(我理解为全局变量,后面就都说全局变量了)的例子。
然后,这里面有关键的数据结构:
id_count
让我们记住一个关键的静态数据:id_count
。表示当前有多少个全局变量。
resource_types_table
这是一个数组,数组的每i
项,存储第i
个全局变量的类型信息。
不同的数据类型的大小resource_types_table[i].size
,构造函数resource_types_table[i].ctor
,析构函数resource_types_table[i].dtor
都不一样。
tsrm_tls_table
请记住这是一个哈希表。
干啥用的?用来真正存储每个线程的『全局变量副本』(请回想这个TSRM是为了干嘛!就是为了给每个线程存储副本!)。
再看这个哈希表怎么做的里面每个元素(Bucket
)是tsrm_tls_entry
,这个哈希表用链表法解决冲突(每个tsrm_tls_entry
有next
指针),哈希函数是用线程idthread_id
对表大小取模计算的坑位。
接下来我们就可以看每个元素tsrm_tls_entry
了
tsrm_tls_entry
真正存储数据的是结构体里的storage
:
void **storage;// 本节点的全局变量数组
这是一个数组,数组里的每一项是个void *
指针,指啥都行(实际上就是要指向这个全局变量的副本!再回忆我们的目标是存副本!)
实际上数组的第i
项就是存的当前线程的第i
个全局变量。
到这里实际上我们就实现了我们的目标『存副本』了!
角色扮演
为了加深理解,现在我们假装自己是一个线程,我的线程id是10
,我现在要找到属于我自己的,id
为1
的全局变量。
- 通过我自己的线程id :
10
, 我可以通过哈希函数算出我自己在哈希表tsrm_tls_table
中的槽位,假设表大小是10,取模正好是0,于是我的槽位是0。然后通过链表解决冲突的方法,沿着链表一个个遍历,发现tsrm_tls_entry->thread_id == 10
,终于找到了属于我的节点。 - 然后我们再提取
tsrm_tls_entry->storage[1]
(id为1的全局变量存在storage[1]里)
注:这里面看源码实际上还会有些细节的处理,不是直接用id在storage里找,不过整体思路是一样的。有兴趣的童鞋可以看下源码。
TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);
3.void ***tslm_ls
就是tslm_tls_entry->storage[i]
这里面不知道为啥要void ***这么多星 求高手指教
全流程走一遍
这是array这个扩展从初始化开始的全流程
1.声明array这个扩展的全局变量
ZEND_DECLARE_MODULE_GLOBALS(array)
按照宏定义
#define ZEND_DECLARE_MODULE_GLOBALS(module_name) \
zend_##module_name##_globals module_name##_globals
展开后就是声明这个玩意儿:
zend_array_globals array_globals
2.初始化
在PHP模块初始化MINIT中会调用
ZEND_INIT_MODULE_GLOBALS(array, php_array_init_globals, NULL);
展开就是
ts_allocate_id(&array_globals_id, sizeof(zend_array_globals), (ts_allocate_ctor) globals_ctror, (ts_allocate_dtor) globals_dtor)
也就是说,会在MINIT阶段,调用这个函数ts_allocate_id
(详见PHP内核)。
3.线程获取变量的地址
通过ARRAYG(v)
获取
#ifdef ZTS
#define ARRAYG(v) TSRMG(array_globals_id, zend_array_globals *, v)
#else
#define ARRAYG(v) (array_globals.v)
#endif
#define TSRMG(id, type, element) (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
展开后是
(((zend_array_glovals *) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(array_globals_id)])->v)
解读(请看清楚括号关系):在tsrm_ls
这个表里取第array_globals_id
项,强制转换成一个zend_array_glovals *
指针,指向这个结构体,然后取里面的成员v
。
感想
1.读各种宏真的太累了
2.纪念第一次如此认真看源码(同时对参考的两篇好文章深入地读)
3.但行好事,莫问前程