摘要:如果現(xiàn)有子進(jìn)程中的線程總數(shù)不能滿足負(fù)載,控制進(jìn)程將派生新的子進(jìn)程。為解決線程的并發(fā)問題,引入了線程安全資源管理器。的全拼,用來存放各個(gè)線程的鏈表。
PHP 進(jìn)階之路 - 零基礎(chǔ)構(gòu)建自己的服務(wù)治理框架(上)
PHP 進(jìn)階之路 - 零基礎(chǔ)構(gòu)建自己的服務(wù)治理框架(下)
PHP 進(jìn)階之路 - 億級(jí) pv 網(wǎng)站架構(gòu)的技術(shù)細(xì)節(jié)與套路
PHP 進(jìn)階之路 - 億級(jí) pv 網(wǎng)站架構(gòu)實(shí)戰(zhàn)之性能壓榨
注:本篇非我一己之力所完成,最后發(fā)布在了《TIPI》這本電子書上。
了解線程安全之前,我們先回顧幾點(diǎn)基礎(chǔ)知識(shí)點(diǎn),是我們后面分析學(xué)習(xí)的基礎(chǔ)。
變量的作用域從作用域上來說,C語言可以定義4種不同的變量:全局變量,靜態(tài)全局變量,局部變量,靜態(tài)局部變量。
下面僅從函數(shù)作用域的角度分析一下不同的變量,假設(shè)所有變量聲明不重名。
全局變量(int gVar;),在函數(shù)外聲明。全局變量,所有函數(shù)共享,在任何地方出現(xiàn)這個(gè)變量名都是指這個(gè)變量。
靜態(tài)全局變量(static sgVar),其實(shí)也是所有函數(shù)共享,但是這個(gè)會(huì)有編譯器的限制,算是編譯器提供的一種功能。
局部變量(函數(shù)/塊內(nèi)的int var;),不共享,函數(shù)的多次執(zhí)行中涉及的這個(gè)變量都是相互獨(dú)立的,他們只是重名的不同變量而已。
局部靜態(tài)變量(函數(shù)中的static int sVar;),本函數(shù)間共享,函數(shù)的每一次執(zhí)行中涉及的這個(gè)變量都是這個(gè)同一個(gè)變量。
上面幾種作用域都是從函數(shù)的角度來定義作用域的,可以滿足所有我們對(duì)單線程編程中變量的共享情況。 現(xiàn)在我們來分析一下多線程的情況。
在多線程中,多個(gè)線程共享除函數(shù)調(diào)用棧之外的其他資源。 因此上面幾種作用域從定義來看就變成了。
全局變量,所有函數(shù)共享,因此所有的線程共享,不同線程中出現(xiàn)的不同變量都是這同一個(gè)變量。
靜態(tài)全局變量,所有函數(shù)共享,也是所有線程共享。
局部變量,此函數(shù)的各次執(zhí)行中涉及的這個(gè)變量沒有聯(lián)系,因此,也是各個(gè)線程間也是不共享的。
靜態(tài)局部變量,本函數(shù)間共享,函數(shù)的每次執(zhí)行涉及的這個(gè)變量都是同一個(gè)變量,因此,各個(gè)線程是共享的。
線程安全資源管理器的由來在多線程系統(tǒng)中,進(jìn)程保留著資源所有權(quán)的屬性,而多個(gè)并發(fā)執(zhí)行流是執(zhí)行在進(jìn)程中運(yùn)行的線程。 如 Apache2 中的 worker,主控制進(jìn)程生成多個(gè)子進(jìn)程,每個(gè)子進(jìn)程中包含固定的線程數(shù),各個(gè)線程獨(dú)立地處理請(qǐng)求。 同樣,為了不在請(qǐng)求到來時(shí)再生成線程,MinSpareThreads 和 MaxSpareThreads 設(shè)置了最少和最多的空閑線程數(shù); 而 MaxClients 設(shè)置了所有子進(jìn)程中的線程總數(shù)。如果現(xiàn)有子進(jìn)程中的線程總數(shù)不能滿足負(fù)載,控制進(jìn)程將派生新的子進(jìn)程。
當(dāng) PHP 運(yùn)行在如上類似的多線程服務(wù)器時(shí),此時(shí)的 PHP 處在多線程的生命周期中。 在一定的時(shí)間內(nèi),一個(gè)進(jìn)程空間中會(huì)存在多個(gè)線程,同一進(jìn)程中的多個(gè)線程公用模塊初始化后的全局變量, 如果和 PHP 在 CLI 模式下一樣運(yùn)行腳本,則多個(gè)線程會(huì)試圖讀寫一些存儲(chǔ)在進(jìn)程內(nèi)存空間的公共資源(如在多個(gè)線程公用的模塊初始化后的函數(shù)外會(huì)存在較多的全局變量)。
此時(shí)這些線程訪問的內(nèi)存地址空間相同,當(dāng)一個(gè)線程修改時(shí),會(huì)影響其它線程,這種共享會(huì)提高一些操作的速度, 但是多個(gè)線程間就產(chǎn)生了較大的耦合,并且當(dāng)多個(gè)線程并發(fā)時(shí),就會(huì)產(chǎn)生常見的數(shù)據(jù)一致性問題或資源競(jìng)爭(zhēng)等并發(fā)常見問題, 比如多次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果不一樣。如果每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無寫操作,則這些個(gè)全局變量就是線程安全的,只是這種情況不太現(xiàn)實(shí)。
為解決線程的并發(fā)問題,PHP 引入了 TSRM: 線程安全資源管理器(Thread Safe Resource Manager)。 TRSM 的實(shí)現(xiàn)代碼在 PHP 源碼的 /TSRM 目錄下,調(diào)用隨處可見,通常,我們稱之為 TSRM 層。 一般來說,TSRM 層只會(huì)在被指明需要的時(shí)候才會(huì)在編譯時(shí)啟用(比如,Apache2+worker MPM,一個(gè)基于線程的MPM), 因?yàn)?Win32 下的 Apache 來說,是基于多線程的,所以這個(gè)層在 Win32 下總是被開啟的。
TSRM的實(shí)現(xiàn)進(jìn)程保留著資源所有權(quán)的屬性,線程做并發(fā)訪問,PHP 中引入的 TSRM 層關(guān)注的是對(duì)共享資源的訪問, 這里的共享資源是線程之間共享的存在于進(jìn)程的內(nèi)存空間的全局變量。 當(dāng) PHP 在單進(jìn)程模式下時(shí),一個(gè)變量被聲明在任何函數(shù)之外時(shí),就成為一個(gè)全局變量。
首先定義了如下幾個(gè)非常重要的全局變量(這里的全局變量是多線程共享的)。
/* The memory manager table */ static tsrm_tls_entry **tsrm_tls_table=NULL; static int tsrm_tls_table_size; static ts_rsrc_id id_count; /* The resource sizes table */ static tsrm_resource_type *resource_types_table=NULL; static int resource_types_table_size;
**tsrm_tls_table 的全拼 thread safe resource manager thread local storage table,用來存放各個(gè)線程的 tsrm_tls_entry 鏈表。
tsrm_tls_table_size 用來表示 **tsrm_tls_table 的大小。
id_count 作為全局變量資源的 id 生成器,是全局唯一且遞增的。
*resource_types_table 用來存放全局變量對(duì)應(yīng)的資源。
resource_types_table_size 表示 *resource_types_table 的大小。
其中涉及到兩個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu) tsrm_tls_entry 和 tsrm_resource_type。
typedef struct _tsrm_tls_entry tsrm_tls_entry; struct _tsrm_tls_entry { void **storage;// 本節(jié)點(diǎn)的全局變量數(shù)組 int count;// 本節(jié)點(diǎn)全局變量數(shù) THREAD_T thread_id;// 本節(jié)點(diǎn)對(duì)應(yīng)的線程 ID tsrm_tls_entry *next;// 下一個(gè)節(jié)點(diǎn)的指針 }; typedef struct { size_t size;// 被定義的全局變量結(jié)構(gòu)體的大小 ts_allocate_ctor ctor;// 被定義的全局變量的構(gòu)造方法指針 ts_allocate_dtor dtor;// 被定義的全局變量的析構(gòu)方法指針 int done; } tsrm_resource_type;
當(dāng)新增一個(gè)全局變量時(shí),id_count 會(huì)自增1(加上線程互斥鎖)。然后根據(jù)全局變量需要的內(nèi)存、構(gòu)造函數(shù)、析構(gòu)函數(shù)生成對(duì)應(yīng)的資源tsrm_resource_type,存入 *resource_types_table,再根據(jù)該資源,為每個(gè)線程的所有tsrm_tls_entry節(jié)點(diǎn)添加其對(duì)應(yīng)的全局變量。
有了這個(gè)大致的了解,下面通過仔細(xì)分析 TSRM 環(huán)境的初始化和資源 ID 的分配來理解這一完整的過程。
TSRM 環(huán)境的初始化模塊初始化階段,在各個(gè) SAPI main 函數(shù)中通過調(diào)用 tsrm_startup 來初始化 TSRM 環(huán)境。tsrm_startup 函數(shù)會(huì)傳入兩個(gè)非常重要的參數(shù),一個(gè)是 expected_threads,表示預(yù)期的線程數(shù), 一個(gè)是 expected_resources,表示預(yù)期的資源數(shù)。不同的 SAPI 有不同的初始化值,比如mod_php5,cgi 這些都是一個(gè)線程一個(gè)資源。
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename) { /* code... */ tsrm_tls_table_size = expected_threads; // SAPI 初始化時(shí)預(yù)計(jì)分配的線程數(shù),一般都為1 tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *)); /* code... */ id_count=0; resource_types_table_size = expected_resources; // SAPI 初始化時(shí)預(yù)先分配的資源表大小,一般也為1 resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type)); /* code... */ return 1; }
精簡(jiǎn)出其中完成的三個(gè)重要的工作,初始化了 tsrm_tls_table 鏈表、resource_types_table 數(shù)組,以及 id_count。而這三個(gè)全局變量是所有線程共享的,實(shí)現(xiàn)了線程間的內(nèi)存管理的一致性。
資源 ID 的分配我們知道初始化一個(gè)全局變量時(shí)需要使用 ZEND_INIT_MODULE_GLOBALS 宏(下面的數(shù)組擴(kuò)展的例子中會(huì)有說明),而其實(shí)際則是調(diào)用的 ts_allocate_id 函數(shù)在多線程環(huán)境下申請(qǐng)一個(gè)全局變量,然后返回分配的資源 ID。代碼雖然比較多,實(shí)際還是比較清晰,下面附帶注解進(jìn)行說明:
TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) { int i; TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new resource id, %d bytes", size)); // 加上多線程互斥鎖 tsrm_mutex_lock(tsmm_mutex); /* obtain a resource id */ *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++); // 全局靜態(tài)變量 id_count 加 1 TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id)); /* store the new resource type in the resource sizes table */ // 因?yàn)?resource_types_table_size 是有初始值的(expected_resources),所以不一定每次都要擴(kuò)充內(nèi)存 if (resource_types_table_size < id_count) { resource_types_table = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); if (!resource_types_table) { tsrm_mutex_unlock(tsmm_mutex); TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); *rsrc_id = 0; return 0; } resource_types_table_size = id_count; } // 將全局變量結(jié)構(gòu)體的大小、構(gòu)造函數(shù)和析構(gòu)函數(shù)都存入 tsrm_resource_type 的數(shù)組 resource_types_table 中 resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].size = size; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; /* enlarge the arrays for the already active threads */ // PHP內(nèi)核會(huì)接著遍歷所有線程為每一個(gè)線程的 tsrm_tls_entry for (i=0; icount < id_count) { int j; p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count); for (j=p->count; j storage[j] = (void *) malloc(resource_types_table[j].size); if (resource_types_table[j].ctor) { // 最后對(duì) p->storage[j] 地址存放的全局變量進(jìn)行初始化, // 這里 ts_allocate_ctor 函數(shù)的第二個(gè)參數(shù)不知道為什么預(yù)留,整個(gè)項(xiàng)目中實(shí)際都未用到過,對(duì)比PHP7發(fā)現(xiàn)第二個(gè)參數(shù)也的確已經(jīng)移除了 resource_types_table[j].ctor(p->storage[j], &p->storage); } } p->count = id_count; } p = p->next; } } // 取消線程互斥鎖 tsrm_mutex_unlock(tsmm_mutex); TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new resource id %d", *rsrc_id)); return *rsrc_id; }
當(dāng)通過 ts_allocate_id 函數(shù)分配全局資源 ID 時(shí),PHP 內(nèi)核會(huì)先加上互斥鎖,確保生成的資源 ID 的唯一,這里鎖的作用是在時(shí)間維度將并發(fā)的內(nèi)容變成串行,因?yàn)椴l(fā)的根本問題就是時(shí)間的問題。當(dāng)加鎖以后,id_count 自增,生成一個(gè)資源 ID,生成資源 ID 后,就會(huì)給當(dāng)前資源 ID 分配存儲(chǔ)的位置,
每一個(gè)資源都會(huì)存儲(chǔ)在 resource_types_table 中,當(dāng)一個(gè)新的資源被分配時(shí),就會(huì)創(chuàng)建一個(gè) tsrm_resource_type。
所有 tsrm_resource_type 以數(shù)組的方式組成 tsrm_resource_table,其下標(biāo)就是這個(gè)資源的 ID。
其實(shí)我們可以將 tsrm_resource_table 看做一個(gè) HASH 表,key 是資源 ID,value 是 tsrm_resource_type 結(jié)構(gòu)(任何一個(gè)數(shù)組都可以看作一個(gè) HASH 表,如果數(shù)組的key 值有意義的話)。
在分配了資源 ID 后,PHP 內(nèi)核會(huì)接著遍歷所有線程為每一個(gè)線程的 tsrm_tls_entry 分配這個(gè)線程全局變量需要的內(nèi)存空間。
這里每個(gè)線程全局變量的大小在各自的調(diào)用處指定(也就是全局變量結(jié)構(gòu)體的大?。W詈髮?duì)地址存放的全局變量進(jìn)行初始化。
為此我畫了一張圖予以說明
上圖中 tsrm_tls_table 的元素是如何添加的,鏈表是如何實(shí)現(xiàn)的。我們把這個(gè)問題先留著,后面會(huì)討論。
每一次的 ts_allocate_id 調(diào)用,PHP 內(nèi)核都會(huì)遍歷所有線程并為每一個(gè)線程分配相應(yīng)資源,
如果這個(gè)操作是在PHP生命周期的請(qǐng)求處理階段進(jìn)行,豈不是會(huì)重復(fù)調(diào)用?
PHP 考慮了這種情況,ts_allocate_id 的調(diào)用在模塊初始化時(shí)就調(diào)用了。
TSRM 啟動(dòng)后,在模塊初始化過程中會(huì)遍歷每個(gè)擴(kuò)展的模塊初始化方法,
擴(kuò)展的全局變量在擴(kuò)展的實(shí)現(xiàn)代碼開頭聲明,在 MINIT 方法中初始化。
其在初始化時(shí)會(huì)知會(huì) TSRM 申請(qǐng)的全局變量以及大小,這里所謂的知會(huì)操作其實(shí)就是前面所說的 ts_allocate_id 函數(shù)。
TSRM 在內(nèi)存池中分配并注冊(cè),然后將資源ID返回給擴(kuò)展。
以標(biāo)準(zhǔn)的數(shù)組擴(kuò)展為例,首先會(huì)聲明當(dāng)前擴(kuò)展的全局變量。
ZEND_DECLARE_MODULE_GLOBALS(array)
然后在模塊初始化時(shí)會(huì)調(diào)用全局變量初始化宏初始化 array,比如分配內(nèi)存空間操作。
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... */ }
這里的聲明和初始化操作都是區(qū)分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
對(duì)于非ZTS的情況,直接聲明變量,初始化變量;對(duì)于ZTS情況,PHP內(nèi)核會(huì)添加TSRM,不再是聲明全局變量,而是用ts_rsrc_id代替,初始化時(shí)也不再是初始化變量,而是調(diào)用ts_allocate_id函數(shù)在多線程環(huán)境中給當(dāng)前這個(gè)模塊申請(qǐng)一個(gè)全局變量并返回資源ID。其中,資源ID變量名由模塊名加global_id組成。
如果要調(diào)用當(dāng)前擴(kuò)展的全局變量,則使用:ARRAYG(v),這個(gè)宏的定義:
#ifdef ZTS #define ARRAYG(v) TSRMG(array_globals_id, zend_array_globals *, v) #else #define ARRAYG(v) (array_globals.v) #endif
如果是非ZTS則直接調(diào)用全局變量的屬性字段,如果是ZTS,則需要通過TSRMG獲取變量。
TSRMG的定義:
#define TSRMG(id, type, element) (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
去掉這一堆括號(hào),TSRMG宏的意思就是從tsrm_ls中按資源ID獲取全局變量,并返回對(duì)應(yīng)變量的屬性字段。
那么現(xiàn)在的問題是這個(gè) tsrm_ls 從哪里來的?
tsrm_ls 的初始化tsrm_ls 通過 ts_resource(0) 初始化。展開實(shí)際最后調(diào)用的是 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) { // 初始化時(shí) th_id = NULL; if (!th_id) { //第一次為空 還未執(zhí)行過 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_lock(tsmm_mutex); // 直接取余,將其值作為數(shù)組下標(biāo),將不同的線程散列分布在 tsrm_tls_table 中 hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size); // 在 SAPI 調(diào)用 tsrm_startup 之后,tsrm_tls_table_size = expected_threads thread_resources = tsrm_tls_table[hash_value]; if (!thread_resources) { // 如果還沒,則新分配。 allocate_new_resource(&tsrm_tls_table[hash_value], thread_id); // 分配完畢之后再執(zhí)行到下面的 else 區(qū)間 return ts_resource_ex(id, &thread_id); } else { do { // 沿著鏈表逐個(gè)匹配 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_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count); // 解鎖 pthread_mutex_unlock(tsmm_mutex); }
而 allocate_new_resource 則是為新的線程在對(duì)應(yīng)的鏈表中分配內(nèi)存,并且將所有的全局變量都加入到其 storage 指針數(shù)組中。
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id) { int i; (*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry)); (*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count); (*thread_resources_ptr)->count = id_count; (*thread_resources_ptr)->thread_id = thread_id; (*thread_resources_ptr)->next = NULL; // 設(shè)置線程本地存儲(chǔ)變量。在這里設(shè)置之后,再到 ts_resource_ex 里取 pthread_setspecific(*thread_resources_ptr); if (tsrm_new_thread_begin_handler) { tsrm_new_thread_begin_handler(thread_id, &((*thread_resources_ptr)->storage)); } for (i=0; istorage[i] = NULL; } else { // 為新增的 tsrm_tls_entry 節(jié)點(diǎn)添加 resource_types_table 的資源 (*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size); if (resource_types_table[i].ctor) { resource_types_table[i].ctor((*thread_resources_ptr)->storage[i], &(*thread_resources_ptr)->storage); } } } if (tsrm_new_thread_end_handler) { tsrm_new_thread_end_handler(thread_id, &((*thread_resources_ptr)->storage)); } pthread_mutex_unlock(tsmm_mutex); }
上面有一個(gè)知識(shí)點(diǎn),Thread Local Storage ,現(xiàn)在有一全局變量 tls_key,所有線程都可以使用它,改變它的值。
表面上看起來這是一個(gè)全局變量,所有線程都可以使用它,而它的值在每一個(gè)線程中又是多帶帶存儲(chǔ)的。這就是線程本地存儲(chǔ)的意義。
那么如何實(shí)現(xiàn)線程本地存儲(chǔ)呢?
需要聯(lián)合 tsrm_startup, ts_resource_ex, allocate_new_resource 函數(shù)并配以注釋一起舉例說明:
// 以 pthread 為例 // 1. 首先定義了 tls_key 全局變量 static pthread_key_t tls_key; // 2. 然后在 tsrm_startup 調(diào)用 pthread_key_create() 來創(chuàng)建該變量 pthread_key_create( &tls_key, 0 ); // 3. 在 allocate_new_resource 中通過 tsrm_tls_set 將 *thread_resources_ptr 指針變量存入了全局變量 tls_key 中 tsrm_tls_set(*thread_resources_ptr);// 展開之后為 pthread_setspecific(*thread_resources_ptr); // 4. 在 ts_resource_ex 中通過 tsrm_tls_get() 獲取在該線程中設(shè)置的 *thread_resources_ptr // 多線程并發(fā)操作時(shí),相互不會(huì)影響。 thread_resources = tsrm_tls_get();
在理解了 tsrm_tls_table 數(shù)組和其中鏈表的創(chuàng)建之后,再看 ts_resource_ex 函數(shù)中調(diào)用的這個(gè)返回宏
#define TSRM_SAFE_RETURN_RSRC(array, offset, range) if (offset==0) { return &array; } else { return array[TSRM_UNSHUFFLE_RSRC_ID(offset)]; }
就是根據(jù)傳入 tsrm_tls_entry 和 storage 的數(shù)組下標(biāo) offset ,然后返回該全局變量在該線程的 storage數(shù)組中的地址。到這里就明白了在多線程中獲取全局變量宏 TSRMG 宏定義了。
其實(shí)這在我們寫擴(kuò)展的時(shí)候會(huì)經(jīng)常用到:
#define TSRMLS_D void ***tsrm_ls /* 不帶逗號(hào),一般是唯一參數(shù)的時(shí)候,定義時(shí)用 */ #define TSRMLS_DC , TSRMLS_D /* 也是定義時(shí)用,不過參數(shù)前面有其他參數(shù),所以需要個(gè)逗號(hào) */ #define TSRMLS_C tsrm_ls #define TSRMLS_CC , TSRMLS_C
NOTICE 寫擴(kuò)展的時(shí)候可能很多同學(xué)都分不清楚到底用哪一個(gè),通過宏展開我們可以看到,他們分別是帶逗號(hào)和不帶逗號(hào),以及申明及調(diào)用,那么英語中“D"就是代表:Define,而 后面的"C"是 Comma,逗號(hào),前面的"C"就是Call。
以上為ZTS模式下的定義,非ZTS模式下其定義全部為空。
加個(gè)硬廣告 PHP 程序員技能包里該來點(diǎn)硬貨了! 最近老鐵開了直播,歡迎來捧場(chǎng)!PHP 進(jìn)階之路 - 億級(jí) pv 網(wǎng)站架構(gòu)的技術(shù)細(xì)節(jié)與套路
PHP 進(jìn)階之路 - 億級(jí) pv 網(wǎng)站架構(gòu)實(shí)戰(zhàn)之性能壓榨
PHP 進(jìn)階之路 - 后端多元化之快速切入 Java 開發(fā)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/23169.html
摘要:下面一起學(xué)習(xí)下鳥哥的框架。揭開神秘面紗采用客戶端服務(wù)器模式。在服務(wù)器端,進(jìn)程保持睡眠狀態(tài)直到調(diào)用信息的到達(dá)為止。這和我們外網(wǎng)的原理不都一個(gè)樣么那么我們一起看看高大上的是怎么在玩。整個(gè)傳輸以二進(jìn)制流的形式傳送。 各位老鐵在點(diǎn)贊、收藏的時(shí)候敢不敢報(bào)名小弟的直播分享,絕對(duì)有干貨,絕對(duì)有驚喜!一次早餐錢的投入,可能是薪資的翻倍,可能是視野的拓展! PHP 進(jìn)階之路 - 億級(jí) pv 網(wǎng)站架構(gòu)...
摘要:在看這篇長輪詢之前可以先看看輪詢技術(shù)沒有長,有助于理解長輪詢屬于輪詢的升級(jí)版,在客戶端和服務(wù)端都進(jìn)行了一些改造,使得消耗更低,速度更快。不間斷的通過查詢服務(wù)端。然后客戶端不間斷繼續(xù)發(fā)起請(qǐng)求數(shù)據(jù)不存在,繼續(xù)循環(huán)。 在看這篇Ajax長輪詢之前可以先看看Ajax輪詢技術(shù)(沒有長),有助于理解: Ajax長輪詢屬于Ajax輪詢的升級(jí)版,在客戶端和服務(wù)端都進(jìn)行了一些改造,使得消耗更低,速度更快。...
摘要:解開多云戰(zhàn)略的神秘面紗組織今天需要采取的關(guān)鍵步驟是云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。在多云環(huán)境中,規(guī)模和復(fù)雜性的挑戰(zhàn)只會(huì)增加。當(dāng)一個(gè)組織缺乏對(duì)其多云基礎(chǔ)設(shè)施的可視性時(shí),會(huì)出現(xiàn)許多問題。解開多云戰(zhàn)略的神秘面紗:組織今天需要采取的關(guān)鍵步驟是:云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。Gartner預(yù)測(cè),到...
摘要:解開多云戰(zhàn)略的神秘面紗組織今天需要采取的關(guān)鍵步驟是云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。在多云環(huán)境中,規(guī)模和復(fù)雜性的挑戰(zhàn)只會(huì)增加。當(dāng)一個(gè)組織缺乏對(duì)其多云基礎(chǔ)設(shè)施的可視性時(shí),會(huì)出現(xiàn)許多問題。解開多云戰(zhàn)略的神秘面紗:組織今天需要采取的關(guān)鍵步驟是:云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。Gartner預(yù)測(cè),到...
摘要:解開多云戰(zhàn)略的神秘面紗組織今天需要采取的關(guān)鍵步驟是云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。在多云環(huán)境中,規(guī)模和復(fù)雜性的挑戰(zhàn)只會(huì)增加。當(dāng)一個(gè)組織缺乏對(duì)其多云基礎(chǔ)設(shè)施的可視性時(shí),會(huì)出現(xiàn)許多問題。解開多云戰(zhàn)略的神秘面紗:組織今天需要采取的關(guān)鍵步驟是:云行業(yè)正以閃電般的速度發(fā)展,企業(yè)采用多云技術(shù)由于需要更快的數(shù)字化轉(zhuǎn)型而日益成為主流。Gartner預(yù)測(cè),到...
閱讀 1020·2021-11-25 09:43
閱讀 2326·2019-08-30 15:55
閱讀 3180·2019-08-30 15:44
閱讀 2086·2019-08-29 16:20
閱讀 1477·2019-08-29 12:12
閱讀 1639·2019-08-26 12:19
閱讀 2315·2019-08-26 11:49
閱讀 1744·2019-08-26 11:42