PHP是單線程,還是多線程的呢服鹅?
PHP是多進程凳兵,還是多線程的呢?
...
解決這些問題企软,首先必須先了解線程和進程庐扫。
備注:進程和線程的都是比較抽象的計算機概念,可參閱《漫畫進程與線程》建立一個初步的認(rèn)知仗哨。
計算機資源
經(jīng)典的馮洛伊曼結(jié)構(gòu)中把計算機系統(tǒng)抽象成“CPU+存儲器+IO”三部分聚蝶,計算機資源無外乎兩種:
- 計算單元
CPU是計算單元,單純從CPU角度來看它是一個黑盒藻治,CPU只對輸入的指令和數(shù)據(jù)進行計算碘勉,然后輸出結(jié)果。CPU不負(fù)責(zé)管理計算那些“指令和數(shù)據(jù)”桩卵。換句話來說验靡,CPU只提供計算能力倍宾,并不負(fù)責(zé)分配計算資源。那么計算資源是由誰來分配的呢胜嗓?計算資源是操作系統(tǒng)來分配的高职,也就是操作系統(tǒng)的調(diào)度模塊,由操作系統(tǒng)按照一定的規(guī)則來分配什么時候由誰來獲得CPU的計算資源辞州,比如分時間片怔锌。 - 存儲資源
存儲資源是內(nèi)存、磁盤等存儲設(shè)備的資源变过,操作系統(tǒng)使用虛擬內(nèi)存機制來管理存儲器埃元。從緩存的角度來說,把內(nèi)存作為磁盤的緩存媚狰。進程是面向磁盤的岛杀,為什么這么說呢?進程表示的是一個運行的程序崭孤,程序的代碼段和數(shù)據(jù)段都是存放在磁盤中的类嗤,只是在運行時加載到內(nèi)存中。所以虛擬內(nèi)存面向的是磁盤辨宠,虛擬頁是磁盤的分配遗锣,然后被緩存到物理內(nèi)存的物理頁中。因此嗤形,存儲資源是操作系統(tǒng)由虛擬內(nèi)存機制來管理和分配的精偿,進程則是操作系統(tǒng)分配存儲資源的最小單元。
什么是虛擬內(nèi)存機制派殷,有什么作用呢?
多任務(wù)
現(xiàn)代的操作系統(tǒng)如Windows墓阀、Linux毡惜、UNIX、MacOS等都是支持“多任務(wù)”的操作系統(tǒng)斯撮,那么什么是多任務(wù)呢经伙?
簡單來說,就是操作系統(tǒng)可以同時運行多個任務(wù)∥鸸現(xiàn)在多核CPU已經(jīng)普及帕膜,但在早期單核時代也是可以執(zhí)行多任務(wù)的。由于CPU執(zhí)行代碼都是順序執(zhí)行的溢十,那么單核CPU是如何執(zhí)行多任務(wù)的呢垮刹?
操作系統(tǒng)輪流讓各個讓任務(wù)交替執(zhí)行,表面上看每個任務(wù)都是交替執(zhí)行的张弛,但是由于CPU的執(zhí)行速度很快早抠,在人類的感知中就好像是所有任務(wù)都在同時執(zhí)行一樣。
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實現(xiàn)坛悉,但是由于任務(wù)數(shù)據(jù)遠(yuǎn)遠(yuǎn)超過CPU的核心數(shù)量洋丐。所以,操作系統(tǒng)也會自動把多個任務(wù)輪流調(diào)度到每個核心上去執(zhí)行觉至。
對于操作系統(tǒng)來說,一個任務(wù)就是一個進程。由于每個進程至少要做一件事兒滩字,但有些進程不止同時只做一件事兒。當(dāng)在一個進程中需要同時做多件事情時就需要同時運行多個“子任務(wù)”御吞,我們把進程內(nèi)這些“子任務(wù)”稱為線程麦箍。所以,一個進程至少有一個線程魄藕。
在實際編程中實現(xiàn)多任務(wù)的方式主要有三種:
- 多進程模式
- 多線程模式
- 多進程 + 多線程模式
同時執(zhí)行多個任務(wù)通常各個任務(wù)之間并不是沒有關(guān)系的内列,而是需要相互通信和協(xié)調(diào)。因此背率,多進程和多線程的程序的復(fù)雜度要遠(yuǎn)遠(yuǎn)高于單進程單線程的程序话瞧。
線程是最小的執(zhí)行單元,而進程由至少一個線程組成寝姿。如何調(diào)度進程和線程交排,完全由操作系統(tǒng)決定,程序自己不能決定什么時候執(zhí)行饵筑,執(zhí)行多長時間埃篓。多進程和多線程的程序涉及到同步、數(shù)據(jù)共享等問題根资,編寫起來比單線程更為復(fù)雜架专。
任務(wù)調(diào)度
大部分操作系統(tǒng)如Windows、Linux的任務(wù)調(diào)度是采用時間輪轉(zhuǎn)的搶占式調(diào)度方式玄帕,也就是說部脚,一個任務(wù)執(zhí)行一小段時間后強制暫停去執(zhí)行下一個任務(wù),每個任務(wù)輪流執(zhí)行裤纹。任務(wù)執(zhí)行的一小段時間叫做時間片委刘,任務(wù)正在執(zhí)行的狀態(tài)叫做運行狀態(tài),任務(wù)執(zhí)行一段時間后強制暫停去執(zhí)行下一個任務(wù)鹰椒,被暫停的任務(wù)就處于就緒狀態(tài)并等待下一個屬于它的時間片的到來锡移。這樣往復(fù)循環(huán)每個任務(wù)都得以執(zhí)行,由于CPU的執(zhí)行效率很高漆际,時間片非常短淆珊,在各個任務(wù)之間快速地切換,給人的感覺就是多個任務(wù)在同時運行(并發(fā))奸汇。
進程Process
什么是進程套蒂?
進程是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)钞支,Multics的設(shè)計者在20世紀(jì)60年代首次使用了“進程”這個技術(shù)詞語,它比“作業(yè)”更加通用一些操刀。
進程是對計算機的一種抽象烁挟,進程表示一個邏輯控制流,也就是一種計算過程骨坑,它造成了一個假象撼嗓,好像這個進程一直是獨占CPU資源的。另外欢唾,進程擁有一個獨立的虛擬內(nèi)存地址空間且警,它也造成了一個假象,好像這個進程一直在獨占存儲資源礁遣。
- 進程是正在執(zhí)行的程序
- 進程是正在計算機上執(zhí)行的程序的實例
- 進程是能分配到CPU并由CPU執(zhí)行的實體
- 進程由單一順序的執(zhí)行線程斑芜、一個當(dāng)前狀態(tài)、一組相關(guān)的系統(tǒng)資源三者所描述的活動單元祟霍。
進程包含指令集和系統(tǒng)資源集
- 指令集:指的是程序代碼
- 系統(tǒng)資源集:是指I/O杏头、CPU、內(nèi)存等沸呐。
簡單來說醇王,進程是具有一定獨立功能的程序,在關(guān)于某個數(shù)據(jù)集合上的一次運行活動崭添。換言之寓娩,進程是一個程序在一個數(shù)據(jù)集上的一次動態(tài)執(zhí)行過程,是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位呼渣。
進程一般由程序棘伴、數(shù)據(jù)集、進程控制塊三部分組成:
- 程序:編寫的程序是用來描述進程要完成那些功能以及如何完成
- 數(shù)據(jù)集:是程序在執(zhí)行過程中所需要使用的資源
- 進程控制塊:是用來記錄進程的外部特征屁置,描述進程的執(zhí)行變化過程焊夸,系統(tǒng)可以利用它來控制和管理進程,是系統(tǒng)感知進程存在的唯一標(biāo)志缰犁。
也可以把進程當(dāng)作由一組元素組成的實體淳地,進程的兩個基本元素是:
- 程序代碼:可能被執(zhí)行相同程序的其它進程共享
- 代碼相關(guān)聯(lián)的數(shù)據(jù)集
假如CPU開始執(zhí)行這個程序代碼怖糊,把這個執(zhí)行實體稱為進程帅容。在進程執(zhí)行時,任意給定時間伍伤,進程都可以唯一的被表征為以下元素:
- 進程描述符
進程的唯一標(biāo)識符并徘,用來和其它進程區(qū)分。在Linux中叫做進程ID(pid
)扰魂,它在系統(tǒng)調(diào)用fork
創(chuàng)建子進程時生成麦乞,注意若使用getpid
返回的其實并不是進程ID蕴茴,而是線程組號tgid
。 - 進程狀態(tài)
進程的狀態(tài)包括就緒姐直、掛起倦淀、運行... - 優(yōu)先級
與進程之間執(zhí)行調(diào)度相關(guān),是相對于其它進程而言的声畏。 - 程序計數(shù)器
程序中即將被執(zhí)行的下一條指令的地址撞叽,是內(nèi)核或用戶內(nèi)存空間中的內(nèi)存地址。 - 內(nèi)存指針
包含程序代碼和進程相關(guān)數(shù)據(jù)的指針插龄,以及與其它進程共享內(nèi)存塊的指針愿棋。 - 上下文數(shù)據(jù)
進程執(zhí)行時CPU處理器上寄存器中的數(shù)據(jù)。 - IO狀態(tài)
包含顯式的I/O請求均牢、分配給進程的I/O設(shè)備等糠雨。 - 記賬信息
可能包含CPU處理時間總和、使用的時鐘總和徘跪、時間限制等甘邀。
進程控制塊PCB
進程的構(gòu)成元素會被存放在一個叫做“進程控制塊”(PCB,Processing Control Block)的數(shù)據(jù)結(jié)構(gòu)中真椿,進程控制塊是操作系統(tǒng)能夠支持多進程和多任務(wù)的結(jié)構(gòu)鹃答。
當(dāng)操作系統(tǒng)執(zhí)行進程切換時,會執(zhí)行兩步操作:
- 中斷:中斷當(dāng)前CPU處理中的進程
當(dāng)進程中斷時突硝,操作系統(tǒng)會把程序計數(shù)器测摔、CPU處理器寄存器(對應(yīng)進程控制塊中的上下文數(shù)據(jù))保存到進程控制塊中的相應(yīng)位置,進程狀態(tài)也會發(fā)生變化解恰,可能進入阻塞狀態(tài)锋八,也可能進入就緒狀態(tài)。 - 執(zhí)行:執(zhí)行下一個進程
當(dāng)執(zhí)行下一個進程時护盈,操作系統(tǒng)會按規(guī)則將下一個進程設(shè)置為運行狀態(tài)挟纱,并加載即將要執(zhí)行進程的程序上下文數(shù)據(jù)和程序計數(shù)器等。
不管是中斷還是執(zhí)行腐宋,進程控制塊中的程序計數(shù)器紊服、上下文數(shù)據(jù)、進程狀態(tài)都會發(fā)生變化胸竞。
線程Thread
為什么會存在線程這樣的概念欺嗤?它解決了什么樣的問題呢?
在早期的操作系統(tǒng)中并沒有線程的概念卫枝,進程是擁有資源和獨立運行的最小單位煎饼,也就是程序執(zhí)行的最小單位。任務(wù)調(diào)度采用時間片輪轉(zhuǎn)的搶占式調(diào)度方式校赤,由于進程是任務(wù)調(diào)度的最小單位吆玖,每個進程由各自獨立的一塊內(nèi)存筒溃,使得各個進程之間內(nèi)存地址相互隔離。
隨著計算機的發(fā)展沾乘,對CPU的要求越來越高怜奖,進程之間的切換開銷越來越大,對多個任務(wù)之間上下問切換的效率要求越來越高翅阵,已經(jīng)無法滿足越來越復(fù)雜的程序的要求了烦周。于是就出現(xiàn)了線程,線程是程序執(zhí)行中一個單一的順序控制流程怎顾,是程序執(zhí)行流的最小單元读慎,是處理器調(diào)度和分派的基本單位。
一個進程可以由一個或多個線程槐雾,各個線程之間共享程序的內(nèi)存空間夭委,也就是所在進程的內(nèi)存空間。一個標(biāo)準(zhǔn)的線程由線程ID募强、當(dāng)前指令指針(PC)株灸、寄存器和堆棧組成。而進程由內(nèi)存空間和多個線程組成擎值。
理論上來說慌烧,在Linux內(nèi)核中是沒有線程這個概念的,只有內(nèi)核調(diào)度實體(Kernal Scheduling Entry鸠儿,KSE)這個概念屹蚊。Linux的線程本質(zhì)上是一種輕量級的進程(Light Weight Process, LWP)进每,是通過clone
克隆系統(tǒng)調(diào)用來創(chuàng)建的汹粤。由于進程是一種KSE,線程也是一種KSE田晚。所以線程是操作系統(tǒng)調(diào)度的最小單元嘱兼。
進程是程序執(zhí)行時的一個實例,是程序執(zhí)行到某種程度的數(shù)據(jù)結(jié)構(gòu)的匯集贤徒。從內(nèi)核的角度來看芹壕,進程的就是分配系統(tǒng)資源的基本單位。
線程是進程的一個執(zhí)行流接奈,是CPU調(diào)度和分派的基本單位踢涌。線程是比進程更小的能獨立運行的基本單位。簡單來說鲫趁,進程是資源分配的最小單位斯嚎,線程是程序執(zhí)行的最小單位利虫。
一個進程會由多個線程組成挨厚,線程與同屬一個進程的其它線程共享進程所擁有的全部資源堡僻。
進程有兩個特性部分:資源所有權(quán)和調(diào)度執(zhí)行
- 資源所有權(quán):是指進程包含了進程運行所需的內(nèi)存空間、I/O等資源疫剃。
- 調(diào)度執(zhí)行:是指進程執(zhí)行過程中間的執(zhí)行路徑钉疫,或者說程序的指令執(zhí)行流。
進程的這兩個特性部分是可以分開的巢价,分開后擁有資源所有權(quán)的通常稱為進程牲阁,擁有執(zhí)行代碼的可分派部分的被稱之為線程或輕量級進程。
線程Thread
有“執(zhí)行的線索”的意思在其中壤躲,而進程Process
在多線程環(huán)境中被定義為資源所有者城菊,還會存儲進程的進程控制塊PCB
。
線程的結(jié)構(gòu)
線程的結(jié)構(gòu)與進程不同碉克,每個線程包含四部分:
- 線程狀態(tài):線程的當(dāng)前狀態(tài)
- 一個執(zhí)行棧
- 私有的數(shù)據(jù)區(qū):用于存放每個線程局部變量的靜態(tài)存儲空間
- 寄存器集:用于存儲CPU處理器的一些狀態(tài)
每個進程都有一個進程控制塊和用戶地址空間凌唬,每個線程都有一個獨立的棧和獨立的控制塊,以及一個獨立執(zhí)行上下文漏麦。
線程的執(zhí)行過程
線程的執(zhí)行過程與進程不同客税,每個獨立的線程有一個程序運行的入口、順序執(zhí)行序列和程序的出口撕贞。線程不能夠獨立執(zhí)行更耻,必須依存于進程之中,由于進程提供多個線程執(zhí)行控制捏膨。從邏輯上看秧均,多線程的意義在于一個進程中,有多個執(zhí)行部分可以同時執(zhí)行号涯。此時熬北,進程本身不是基本運行單位,而實線程的容器诚隙。
線程于進程對比而言讶隐,優(yōu)勢在于快,不管是線程的創(chuàng)建還是終止久又,不管是線程間的切換還是線程間共享數(shù)據(jù)或通信巫延,它的速度于進程相比都有較大的優(yōu)勢。
單線程與多線程
進程與線程的關(guān)系
- 一個線程只能屬于一個進程地消,而一個進程可以擁有多個線程炉峰,但至少有一個線程(主線程)。
多線程處理就是允許一個進程中在同一時刻執(zhí)行多個任務(wù) - 線程是一種輕量級的進程脉执,與進程相比疼阔,線程給操作系統(tǒng)帶來的維護和管理的負(fù)擔(dān)要小,以就是說線程的開銷代價更小。
- 系統(tǒng)資源分配給進程婆廊,同一進程中的所有線程共享這個進程的所有資源迅细。
- CPU會分給線程,換句話說淘邻,真正在CPU上運行的是線程茵典。
- 線程沒有地址空間,線程包含在進程的地址空間中宾舅。線程上下文只包含一個堆棧统阿、一個寄存器、一個優(yōu)先權(quán)筹我。線程文本包含在它的進程文本段中扶平,進程擁有的所有資源都屬于線程。所有的線程共享進程的內(nèi)存和資源蔬蕊。同一進程中的多個線程共享代碼段(代碼和常量)蜻直、數(shù)據(jù)段(全局變量和靜態(tài)變量)、擴展段(堆存儲)袁串。但是每個線程擁有自己的堆段(運行時段概而,用來存放所有局部變量和臨時變量)、寄存器內(nèi)容囱修。
- 父子進程使用進程間通信機制赎瑰,同一進程的線程通過讀取和寫入數(shù)據(jù)到進程變量來進行通信。
并發(fā)與并行
并發(fā)
并發(fā)又稱為共行破镰,是指能夠處理多個同時性活動的能力餐曼。并發(fā)事件之間不一定要同一時刻發(fā)生。現(xiàn)代計算機系統(tǒng)可以在同一段時間內(nèi)以進程的形式鲜漩,將多個程序加載到存儲器中源譬,并借由CPU處理的時分復(fù)用,制造出在一個CPU處理器上展現(xiàn)同時運行的錯覺孕似。并行
并行是指同時發(fā)生的兩個并發(fā)事件踩娘,并行具有并發(fā)的含義,并發(fā)則不一定會并行喉祭。
簡單來說养渴,并發(fā)和并行的區(qū)別在于一個CPU處理器同時處理多個任務(wù)和多個CPU處理器或者是多核處理器同時處理多個不同的任務(wù),并發(fā)是邏輯上同時發(fā)生泛烙,而并行則是物理上的同時發(fā)生理卑。
高并發(fā)
可參見《高并發(fā)原理》,后續(xù)待補蔽氨。
多進程與多線程的關(guān)系
使用多進程的優(yōu)勢
- 子進程結(jié)束后藐唠,系統(tǒng)內(nèi)核會負(fù)責(zé)回收資源帆疟。
- 子進程異常退出時不會導(dǎo)致整個進程退出,父進程還有機會重建流程宇立。
- 一個常駐主進程踪宠,只負(fù)責(zé)任務(wù)分發(fā),邏輯會更加清晰泄伪。
- 使用多進程更加穩(wěn)定,另外利用進程間通信IPC也可以實現(xiàn)數(shù)據(jù)共享匿级。
- 使用多進程共享內(nèi)存和線程間讀寫變量是一樣的蟋滴,同樣需要加鎖,會有同步痘绎、死鎖問題津函。
- 使用多進程消息隊列,可以采用多個子進程搶奪隊列模式孤页,性能很好尔苦。
使用多線程得優(yōu)勢
- 線程是在同一個進程內(nèi)的,可以共享內(nèi)存變量實現(xiàn)線程間通信行施。
- 線程比進程更輕量級允坚,開大量進程比線程消耗更多系統(tǒng)資源。
使用多線程存在的問題
- 線程讀寫變量存在同步問題需要加鎖
- 鎖得粒度過大時會存在性能問題蛾号,可能會導(dǎo)致只有一個線程在運行稠项,其它線程都在等待鎖。
- 同時使用多個鎖鲜结,邏輯會很復(fù)雜展运,一旦某個鎖沒有被正確釋放,可能會發(fā)生線程死鎖精刷。
- 某個線程發(fā)生指明錯誤會導(dǎo)致整個進程崩潰
并發(fā)模型
C10K(Client 10000)問題 - 如何單服同時服務(wù)1w個客戶端拗胜?
由于早期服務(wù)器是基于進程/線程模型,每新來一個連接就分配一個進程/線程去處理這個連接怒允,而進程/線程在操作系統(tǒng)中會占用一定的資源的埂软。由于硬件的限制,進程/線程的創(chuàng)建是有瓶頸的纫事。另外仰美,進程/線程的上下文切換也是有成本的,每次調(diào)度器調(diào)度線程儿礼,操作系統(tǒng)都要把線程的各種必要信息如程序計數(shù)器咖杂、堆棧、寄存器蚊夫、狀態(tài)等保存起來诉字。
由于CPU的運算速度遠(yuǎn)快于I/O操作,互聯(lián)網(wǎng)應(yīng)用如Web都是I/O密集型而非計算密集型的。I/O密集型是指計算機CPU大量的時間耗費在等待數(shù)據(jù)的輸入和輸出上壤圃,而不是計算上陵霉。當(dāng)CPU大部分時間都在等待I/O的時候,大部分計算資源是被浪費掉了的伍绳。顯然踊挠,簡單粗暴地開一個進程/線程去處理一個連接時不夠的,為了達(dá)到高并發(fā)冲杀,需要重點思考的是I/O策略(模型)效床,在同樣的硬件條件下不同的設(shè)計會產(chǎn)生很大的差異。
PHP并發(fā)模型
PHP并發(fā)模型可分為多進程模型和多線程模型权谁,那么PHP使用的哪一種呢剩檀?答案是都支持,也就是說PHP支持多線程的模型旺芽,在多線程情況下沪猴,通常要解決的問題是資源共享和隔離,而PHP自身就是線程安全的采章。
那么到底是哪一種呢运嗜?具體來說就需要看PHP所使用的是那個SAPI,例如在Apache中就可能使用多線程模型也可能使用多進程模型悯舟,在PHP-FPM中使用的就是多進程模型洗出。
目前比較推薦的方式是使用PHP-FPM的模型,因為這個模型對PHP來說有諸多優(yōu)勢:
- 內(nèi)存釋放簡單
使用多進程模型時图谷,進程可以很容易通過退出的方式來釋放內(nèi)存翩活,由于PHP有很多擴展,稍有不慎就可能導(dǎo)致內(nèi)存泄漏便贵,PHP-FPM通過進程退出的方式菠镇,簡單粗暴的解決了問題。 - 容災(zāi)能力強
由于PHP擴展或PHP自身可能會出現(xiàn)錯誤承璃,如果是單進程多線程模型利耍,那么整個PHP就掛掉了,這直接就影響到服務(wù)盔粹。多進程的話隘梨,即使某個進程死掉了也不會影響整個服務(wù)。
多進程與多線程各具優(yōu)勢舷嗡,例如HHVM選擇的就是多線程模型轴猎,多線程模型最大的好處是數(shù)據(jù)共享和通信方便,因為在同一個進程空間內(nèi)进萄,可以直接使用指針捻脖。在PHP的opcode cache工具中apc和opcache等使用的是共享內(nèi)存來共享opcode锐峭,而在HHVM中則不需要走共享內(nèi)存。共享內(nèi)存有個問題是存儲復(fù)雜的數(shù)據(jù)結(jié)構(gòu)不方便可婶,因為指針的問題沿癞,多線程情況下C/C++中的數(shù)據(jù)結(jié)構(gòu)是可以共享的。這對效率提升也是有幫助的矛渴。
多進程和多項還有一個明顯的模型區(qū)別是在處理請求時的邏輯上
- 多進程
由于跨進程時不容易傳遞fd
客戶端唯一連接標(biāo)識的椎扬,在多進程中,通常采用的是在父進程中listen()
具温,然后給子進程accept()
的方式來實現(xiàn)負(fù)載均衡蚕涤,這樣的模型下可能會有驚群的問題。 - 多線程
多線程模型下可以采用一個獨立線程接收請求然后派發(fā)到各個worker
工作線程中去桂躏。
多線程的PHP
PHP從代碼級別來講是不支持多線程操作的钻趋,不能像Java川陆、C#等語言一樣編寫多線程代碼剂习。多線程只是代碼運行時在同一時刻同時執(zhí)行多個線程任務(wù),來提高服務(wù)器CPU的利用率较沪。PHP是可以以多進程方式執(zhí)行鳞绕,例如PHP的進程管理工具PHP-FPM的進程管理機制就是采用了多進程單線程的方式,有效提高了并發(fā)訪問的響應(yīng)效率尸曼。
PHP從設(shè)計之初到流行起來都沒有出現(xiàn)明顯需要多線程才能解決問題的需求们何,某些需要多線程的地方也有相應(yīng)的解決方案和替代方案。而且多線程并不總是比單線程具有更多優(yōu)勢控轿,另外多線程可能會引入其它問題冤竹,例如多個線程同時調(diào)用一個類中的同一個方法時,可能出現(xiàn)死鎖的情況茬射。
簡單來說鹦蠕,對于一個客戶端的一個頁面請求處理的PHP是單線程的,這樣做的好處是可以自上而下的編寫和理解代碼中的業(yè)務(wù)邏輯在抛。但在PHP是可以同時開啟多個線程來處理多個客戶端請求的同一個PHP腳本钟病,所以PHP也可以看成是多線程的。
雖然每個PHP腳本的執(zhí)行是單線程的刚梭,但對于Web服務(wù)器組件如Apache/Nginx/PHP-FPM是多線程的肠阱,因為客戶端每次對某個PHP腳本的發(fā)起請求時,Web服務(wù)器都會創(chuàng)建一個新的進程/線程朴读,用來執(zhí)行對應(yīng)的PHP腳本屹徘。也就是說,對于一個客戶端請求來說衅金,PHP是單線程的缘回,但多個請求間是并發(fā)的吆视。
簡單來說,PHP本身是不支持多線程酥宴,但是Web服務(wù)器是支持多線程的啦吧,利用Web服務(wù)器本身的多線程來處理,從Web服務(wù)器多次調(diào)用實現(xiàn)多線程的程序拙寡。也就是說授滓,可以多人同時訪問,這也就是在PHP中實現(xiàn)多線程的基礎(chǔ)肆糕。
PHP線程安全
如何選擇PHP的版本般堆,TS or NTS?
由于Linux/UNIX系統(tǒng)采用多進程的工作方式诚啃,而Windows系統(tǒng)采用多線程的工作方式淮摔。如果在Windows的IIS下以CGI方式運行PHP會非常慢,因為CGI模式是建立在多進程的基礎(chǔ)之上的始赎,而非多線程和橙。所以,在Windows IIS中會把PHP配置成以ISAPI的方式來運行造垛,ISAPI是多線程方式魔招。但存在要給問題,很多PHP擴展時以Linux/UNIX的多進程思想開發(fā)的五辽,這些擴展在ISAPI方式運行時出錯并搞垮IIS办斑。因此,在IIS下CGI模式才是PHP運行的最安全方式杆逗,但CGI對于每個HTTP請求都需要重新記載和卸載整個PHP環(huán)境乡翅,其消耗是巨大的。
為了兼顧IIS下PHP的效率和安全罪郊,Microsoft提出了FastCGI的解決方案蠕蚜,F(xiàn)astCGI可以讓PHP的進程重復(fù)利用,而不是為每個新請求就重開一個進程排龄。同時FastCGI也可以運行多個進程同時執(zhí)行波势,這樣即解決了CGI進程模式消耗太大的問題,又利用上了CGI進程模式不存在線程安全問題的優(yōu)點橄维。
因此尺铣,如果PHP使用ISAPI的方式運行就必修使用線程安全(Thread Safe, TS)的版本争舞,如果使用FastCGI模式就沒有必要使用線程安全檢查凛忿,可采用非線程安全(None Thread Safe, NTS)版本以提高效率竞川。
ZTS是什么店溢?
PHP的SAPI多數(shù)是單線程環(huán)境叁熔,比如CGI、CLI床牧、FPM荣回,每個進程只啟動一個主線程,這種模式下是不存在線程安全問題的戈咳。但也又多線程的環(huán)境心软,如Apache,在這種情況下就需要考慮線程安全問題著蛙。因為PHP中又很多全局變量删铃,如EG、CG踏堡。如果多個線程共享同一個變量將會發(fā)生沖突猎唁,所以PHP為多線程的應(yīng)用模型提供了一個安全機制 - Zend線程安全(Zend Thread Safe,ZTS)顷蟆。
PHP專門為解決線程安全問題抽象出一個線程安全資源管理器(Thread Safe Resource Manager, TSRM)诫隅,實現(xiàn)原理:既然共用資源這么困難那么就不共用,各線程不再共享同一份全局變量慕的,而是各自復(fù)制一份阎肝,使用數(shù)據(jù)時各個線程各自取自己的副本挤渔,互不干擾肮街。