一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - PHP教程 - PHP及Zend Engine的線程安全模型分析

PHP及Zend Engine的線程安全模型分析

2019-12-17 14:20PHP教程網(wǎng) PHP教程

在閱讀PHP源碼和學(xué)習(xí)PHP擴(kuò)展開發(fā)的過程中,我接觸到大量含有“TSRM”字眼的宏。通過查閱資料,知道這些宏與Zend的線程安全機(jī)制有關(guān),而絕大多數(shù)資料中都建議按照既定規(guī)則使用這些宏就可以,而沒有說明這些宏的具體作用

不知道怎么回事總是令人不舒服的,因此我通過閱讀源碼和查閱有限的資料簡要了解一下相關(guān)機(jī)制,本文是我對研究內(nèi)容的總結(jié)。 本文首先解釋了線程安全的概念及PHP中線程安全的背景,然后詳細(xì)研究了PHP的線程安全機(jī)制ZTS(Zend Thread Safety)及具體的實(shí)現(xiàn)TSRM,研究內(nèi)容包括相關(guān)數(shù)據(jù)結(jié)構(gòu)、實(shí)現(xiàn)細(xì)節(jié)及運(yùn)行機(jī)制,最后研究了Zend對于單線程和多線程環(huán)境的選擇性編譯問題。 

線程安全 
線程安全問題,一言以蔽之就是多線程環(huán)境下如何安全存取公共資源。我們知道,每個線程只擁有一個私有棧,共享所屬進(jìn)程的堆。在C中,當(dāng)一個變量被聲明在任何函數(shù)之外時,就成為一個全局變量,這時這個變量會被分配到進(jìn)程的共享存儲空間,不同線程都引用同一個地址空間,因此一個線程如果修改了這個變量,就會影響到全部線程。這看似為線程共享數(shù)據(jù)提供了便利,但是PHP往往是每個線程處理一個請求,因此希望每個線程擁有一個全局變量的副本,而不希望請求間相互干擾。 早期的PHP往往用于單線程環(huán)境,每個進(jìn)程只啟動一個線程,因此不存在線程安全問題。后來出現(xiàn)了多線程環(huán)境下使用PHP的場景,因此Zend引入了Zend線程安全機(jī)制(Zend Thread Safety,簡稱ZTS)用于保證線程的安全。 

ZTS的基本原理及實(shí)現(xiàn) 
基本思想 
說起來ZTS的基本思想是很直觀的,不是就是需要每個全局變量在每個線程都擁有一個副本嗎?那我就提供這樣的機(jī)制: 在多線程環(huán)境下,申請全局變量不再是簡單聲明一個變量,而是整個進(jìn)程在堆上分配一塊內(nèi)存空間用作“線程全局變量池”,在進(jìn)程啟動時初始化這個內(nèi)存池,每當(dāng)有線程需要申請全局變量時,通過相應(yīng)方法調(diào)用TSRM(Thread Safe Resource Manager,ZTS的具體實(shí)現(xiàn))并傳遞必要的參數(shù)(如變量大小等等),TSRM負(fù)責(zé)在內(nèi)存池中分配相應(yīng)內(nèi)存區(qū)塊并將這塊內(nèi)存的引用標(biāo)識返回,這樣下次這個線程需要讀寫此變量時,就可以通過將唯一的引用標(biāo)識傳遞給TSRM,TSRM將負(fù)責(zé)真正的讀寫操作。這樣就實(shí)現(xiàn)了線程安全的全局變量。下圖給出了ZTS原理的示意圖: 

 

Thread1和Thread2同屬一個進(jìn)程,其中各自需要一個全局變量Global Var,TSRM為兩者在線程全局內(nèi)存池中(黃色部分)各自分配了一個區(qū)域,并且通過唯一的ID進(jìn)行標(biāo)識,這樣兩個線程就可以通過TSRM存取自己的變量而互不干擾。 下面通過具體的代碼片段看一下Zend具體是如何實(shí)現(xiàn)這個機(jī)制的。這里我用的是PHP5.3.8的源碼。 TSRM的實(shí)現(xiàn)代碼在PHP源碼的“TSRM”目錄下。 

數(shù)據(jù)結(jié)構(gòu) 
TSRM中比較重要的數(shù)據(jù)結(jié)構(gòu)有兩個:tsrm_tls_entry和tsrm_resource_type。下面先看tsrm_tls_entry。 tsrm_tls_entry定義在TSRM/TSRM.c中: 

復(fù)制代碼代碼如下:


typedef struct _tsrm_tls_entry tsrm_tls_entry; 

struct _tsrm_tls_entry { 
void **storage; 
int count; 
THREAD_T thread_id; 
tsrm_tls_entry *next; 


每個tsrm_tls_entry結(jié)構(gòu)負(fù)責(zé)表示一個線程的所有全局變量資源,其中thread_id存儲線程ID,count記錄全局變量數(shù),next指向下一個節(jié)點(diǎn)。storage可以看做指針數(shù)組,其中每個元素是一個指向本節(jié)點(diǎn)代表線程的一個全局變量。最終各個線程的tsrm_tls_entry被組成一個鏈表結(jié)構(gòu),并將鏈表頭指針賦值給一個全局靜態(tài)變量tsrm_tls_table。注意,因?yàn)閠srm_tls_table是一個貨真價實(shí)的全局變量,所以所有線程會共享這個變量,這就實(shí)現(xiàn)了線程間的內(nèi)存管理一致性。tsrm_tls_entry和tsrm_tls_table結(jié)構(gòu)的示意圖如下: 

 

tsrm_resource_type的內(nèi)部結(jié)構(gòu)相對簡單一些: 

復(fù)制代碼代碼如下:


typedef struct { 
size_t size; 
ts_allocate_ctor ctor; 
ts_allocate_dtor dtor; 
int done; 


tsrm_resource_type;上文說過tsrm_tls_entry是以線程為單位的(每個線程一個節(jié)點(diǎn)),而tsrm_resource_type以資源(或者說全局變量)為單位,每次一個新的資源被分配時,就會創(chuàng)建一個tsrm_resource_type。所有tsrm_resource_type以數(shù)組(線性表)的方式組成tsrm_resource_table,其下標(biāo)就是這個資源的ID。每個tsrm_resource_type存儲了此資源的大小和構(gòu)造、析構(gòu)方法指針。某種程度上,tsrm_resource_table可以看做是一個哈希表,key是資源ID,value是tsrm_resource_type結(jié)構(gòu)。 

實(shí)現(xiàn)細(xì)節(jié) 
這一小節(jié)分析TSRM一些算法的實(shí)現(xiàn)細(xì)節(jié)。因?yàn)檎麄€TSRM涉及代碼比較多,這里揀其中具有代表性的兩個函數(shù)分析。 第一個值得注意的是tsrm_startup函數(shù),這個函數(shù)在進(jìn)程起始階段被sapi調(diào)用,用于初始化TSRM的環(huán)境。由于tsrm_startup略長,這里摘錄出我認(rèn)為應(yīng)該注意的地方: 

復(fù)制代碼代碼如下:


/* Startup TSRM (call once for the entire process) */ 
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename) 

/* code... */ 

tsrm_tls_table_size = expected_threads; 

tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *)); 
if (!tsrm_tls_table) { 
TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate TLS table")); 
return 0; 

id_count=0; 

resource_types_table_size = expected_resources; 
resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type)); 
if (!resource_types_table) { 
TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate resource types table")); 
free(tsrm_tls_table); 
tsrm_tls_table = NULL; 
return 0; 


/* code... */ 

return 1; 


其實(shí)tsrm_startup的主要任務(wù)就是初始化上文提到的兩個數(shù)據(jù)結(jié)構(gòu)。第一個比較有意思的是它的前兩個參數(shù):expected_threads和expected_resources。這兩個參數(shù)由sapi傳入,表示預(yù)計的線程數(shù)和資源數(shù),可以看到tsrm_startup會按照這兩個參數(shù)預(yù)先分配空間(通過calloc)。因此TSRM會首先分配可容納expected_threads個線程和expected_resources個資源的。要看各個sapi默認(rèn)會傳入什么,可以看各個sapi的源碼(在sapi目錄下),我簡單看了一下: 

 

可以看到比較常用的sapi如mod_php5、php-fpm和cgi都是預(yù)分配一個線程和一個資源,這樣是因?yàn)椴辉咐速M(fèi)內(nèi)存空間,而且多數(shù)情況下PHP還是運(yùn)行于單線程環(huán)境。 這里還可以看到一個id_count變量,這個變量是一個全局靜態(tài)變量,其作用就是通過自增產(chǎn)生資源ID,這個變量在這里被初始化為0。所以TSRM產(chǎn)生資源ID的方式非常簡單:就是一個整形變量的自增。 第二個需要仔細(xì)分析的就是ts_allocate_id,編寫過PHP擴(kuò)展的朋友對這個函數(shù)肯定不陌生,這個函數(shù)...

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 肉文高h文 | 美女bbxx美女bbb | 亚洲mv国产精品mv日本mv | 色人阁导航 | 精品久久成人免费第三区 | 日本精品中文字幕在线播放 | 精品国产欧美一区二区五十路 | 四虎影库网址 | 天天澡夜夜澡狠狠澡 | 茄子香蕉视频 | xxoo做爰猛烈动态 | 美女用手扒开粉嫩的屁股 | 亚洲国产精品第一页 | 久久精品一区 | 91入口免费网站大全 | 99国产在线视频 | 欧美牛逼aa | 色综合亚洲天天综合网站 | 韩国一区二区三区 | 亚洲天天做夜夜做天天欢 | 精品久久成人免费第三区 | 99视频免费在线 | 免费国产一级观看完整版 | 草草草草视频 | 成人另类视频 | 国产精品高清一区二区三区 | ady久久| 亚洲视频一区二区在线观看 | 丰满的闺蜜2中文字幕 | 国产一级毛片外aaaa | 好湿好滑好硬好爽好深视频 | 美女扒开两腿露出尿口的视频 | 国偷盗摄自产福利一区在线 | 青久草视频 | 公交车上插入 | 亚洲品质自拍网站 | 精品久久久久久久久久香蕉 | s8sp加密路线和免费路线首页 | 色吊丝每日永久访问网站 | 亚洲国产欧美在线人网站 | 免费标准高清看机机桶机机 |