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()直接取到了,節省加鎖檢索的時間。安全