成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

PHP 進(jìn)階之路 - 揭開 PHP 線程安全的神秘面紗

pepperwang / 3460人閱讀

摘要:如果現(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_entrytsrm_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; jstorage[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_entrystorage 的數(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

相關(guān)文章

  • PHP 教父鳥哥 Yar 原理分析

    摘要:下面一起學(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)...

    B0B0 評(píng)論0 收藏0
  • 用大白話揭開Ajax長輪詢(long polling)神秘面紗

    摘要:在看這篇長輪詢之前可以先看看輪詢技術(shù)沒有長,有助于理解長輪詢屬于輪詢的升級(jí)版,在客戶端和服務(wù)端都進(jìn)行了一些改造,使得消耗更低,速度更快。不間斷的通過查詢服務(wù)端。然后客戶端不間斷繼續(xù)發(fā)起請(qǐng)求數(shù)據(jù)不存在,繼續(xù)循環(huán)。 在看這篇Ajax長輪詢之前可以先看看Ajax輪詢技術(shù)(沒有長),有助于理解: Ajax長輪詢屬于Ajax輪詢的升級(jí)版,在客戶端和服務(wù)端都進(jìn)行了一些改造,使得消耗更低,速度更快。...

    AlphaGooo 評(píng)論0 收藏0
  • 揭開多重身份神秘面紗

    摘要:解開多云戰(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è),到...

    lscho 評(píng)論0 收藏0
  • 揭開多重身份神秘面紗

    摘要:解開多云戰(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è),到...

    zhangke3016 評(píng)論0 收藏0
  • 揭開多重身份神秘面紗

    摘要:解開多云戰(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è),到...

    付倫 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<