1.线程安全资源管理器数组
PHP的SAPI多数是单线程环境,好比cli、fpm、cgi,每一个进程只启动一个主线程,这种模式下是不存在线程安全问题的,可是也有多线程的环境,好比Apache,这种状况下就须要考虑线程安全的问题了,由于PHP中有不少全局变量,好比最多见的:EG、CG,若是多个线程共享同一个变量将会冲突,因此PHP为多线程的应用模型提供了一个安全机制:Zend线程安全(Zend Thread Safe, ZTS)。
PHP中专门为解决线程安全的问题抽象出了一个线程安全资源管理器(Thread Safe Resource Mananger, TSRM),实现原理比较简单:既然共用资源这么困难那么就干脆不共用,各线程再也不共享同一份全局变量,而是各复制一份,使用数据时各线程各取本身的副本,互不干扰。
typedef struct { size_t size; //资源的大小 ts_allocate_ctor ctor; //初始化函数 ts_allocate_dtor dtor; int done; } tsrm_resource_type; struct _tsrm_tls_entry { void **storage; //资源数组 int count; //拥有的资源数:storage数组大小 THREAD_T thread_id; //所属线程id tsrm_tls_entry *next; };
若是一个资源会被多线程使用,那么首先须要预先向TSRM注册资源,而后TSRM为这个资源分配一个惟一的编号,并把这种资源的大小、初始化函数等保存到一个tsrm_resource_type结构中,各线程只能经过TSRM分配的那个编号访问这个资源;而后当线程拿着这个编号获取资源时TSRM若是发现是第一次请求,则会根据注册时的资源大小分配一块内存,而后调用初始化函数进行初始化,并把这块资源保存下来供这个线程后续使用。
每一个线程拥有一个tsrm_tls_entry结构,当前线程的全部资源保存在storage数组中,下标就是各资源的id。另外全部线程的tsrm_tls_entry结构经过一个数组保存:tsrm_tls_table,这是个全局变量,每一个线程的tsrm_tls_entry结构在这个数组中的位置是根据线程id与预设置的线程数(tsrm_tls_table_size)取模获得的,也就是说有可能多个线程保存在tsrm_tls_table同一位置,因此tsrm_tls_entry是个链表,查找资源时首先根据:线程id % tsrm_tls_table_size获得一个tsrm_tls_entry,而后开始遍历链表比较thread_id肯定是不是当前线程的。线程本地存储(Thread Local Storage, TLS),在建立完当前线程的tsrm_tls_entry后会把这个值保存到当前线程的TLS中,这样在ts_resource()获取资源时中就能够经过tsrm_tls_get()直接取到了,节省加锁检索的时间。安全