我是一名后端開發(fā)工程師,目前從事后端開發(fā)有8年的經(jīng)驗(yàn),主要以PHP開發(fā)為主,期間有1年時(shí)間使用C語言進(jìn)行PHP拓展的開發(fā)工作,
工作主要圍繞輔助線程及有棧協(xié)程調(diào)度器;還有2年主要使用Golang做一些基礎(chǔ)服務(wù)。
這篇文章我會(huì)用一些通俗的話來描述一些我所了解的程序的一些點(diǎn),如 異步、同步、阻塞、非阻塞等;當(dāng)然,我的經(jīng)驗(yàn)不算豐富也可能有些理解錯(cuò)誤的地方,
也希望能夠指正我描述有錯(cuò)誤的部分。
程序最開始就如同寫文章一般,從上至下,然后伴隨著一個(gè)return結(jié)束:
#include<stdio.h>
main(){
return 0;
}
這個(gè)程序?qū)τ谙到y(tǒng)來說是一個(gè)唯一入口,有點(diǎn)類似于PHP的index.php入口對(duì)于PHP zendVM一樣,也是唯一入口:
<?php
# 啥也沒有
?>
道理是這么一個(gè)道理,Golang也一樣,Java也一樣;
這樣的程序其實(shí)跟腳本是類似的,從上執(zhí)行到結(jié)束、退出;既然這樣的程序和腳本類似,我么就把它比作一張紙,
我們可以在這張紙上胡作非為,寫上A\B\C\D隨便什么都可以,它就會(huì)按照從上到下直到結(jié)束:
如上圖,這個(gè)程序就完成了一個(gè)結(jié)合了A+B+C+D的業(yè)務(wù)邏輯,我們稱它ABCD;
這時(shí)候你可能又會(huì)想,如果我要完成ABC的業(yè)務(wù)邏輯,怎么辦?有兩種辦法:
于是我們豐富了一下程序,利用了編程語言基礎(chǔ)的邏輯判斷的特性:
但是我們不滿足于此,因?yàn)锳\B\C\D都有可能帶來判斷,甚至可能加入E\F\G等等等等……
那么這時(shí)候,程序如果按照這樣的發(fā)展趨勢(shì),應(yīng)該會(huì)變成:
CAO!這肯定不是每個(gè)程序員想要的結(jié)果!
那么為了拯救這樣的情況出現(xiàn),我們索性用多張紙且明確劃分區(qū)域地來完成這樣的復(fù)雜的、
多功能的程序,每個(gè)紙張上都各自有不同的ABCD或EFGH,這種編寫程序的方式就是面向?qū)ο缶幊蹋?br />
當(dāng)然過程對(duì)象也可以達(dá)到這個(gè)效果,只不過需要引入各式各樣的紙張,且不同紙張內(nèi)容可能出現(xiàn)重復(fù),
甚至不符合人的思維邏輯:
我們上述的所有程序,都不能自動(dòng)的反復(fù)的執(zhí)行;
這里面包括C、Golang等語言,只要不引入一些庫就純粹的運(yùn)行來說,它們和腳本是無異的
但是我們?cè)谏钪袩o時(shí)不刻的做著重復(fù)的事兒,畢竟人類的本質(zhì)是復(fù)讀機(jī)嘛;那么我們程序怎么做到重復(fù)執(zhí)行呢?
有以下兩種辦法:
懶人能推動(dòng)社會(huì)進(jìn)步,我選B。
那么為了實(shí)現(xiàn)這樣的程序,我們?cè)撛趺醋瞿?,怎么讓這個(gè)程序不斷地重復(fù)重復(fù)再重復(fù)呢?這時(shí)候可以利用語言特性:
循環(huán),在循環(huán)的作用下,由上至下的程序則會(huì)變成這個(gè)樣子:
wocao!它轉(zhuǎn)起來了,轉(zhuǎn)起來了!這不就是常駐內(nèi)存的程序嗎?當(dāng)然,這里面我省略了一些接受系統(tǒng)通知、監(jiān)控的步驟,
那么我們就假設(shè)這個(gè)圈就是一個(gè)系統(tǒng),每個(gè)紙張就是程序,循環(huán)飛速的運(yùn)轉(zhuǎn),那么每個(gè)程序都可以快速的運(yùn)轉(zhuǎn),
這樣一看,它是不是就變得豐富形象且生動(dòng)了?
我們把這個(gè)”系統(tǒng)“的目光收一收,把目光再次聚集到每張紙張上面的A\B\C\D,你會(huì)發(fā)現(xiàn)ABCD該順序執(zhí)行的還是順序執(zhí)行,
紙張1沒執(zhí)行完,紙張2就不會(huì)到,因?yàn)樵谶@個(gè)循環(huán)的圈中也是順序執(zhí)行的,怎么辦呢?我能不能把A\B\C\D打散?
把每張紙聚焦在該做的事兒上?可以。
這里我們可以把這些A\B\C\D理解為系統(tǒng)的一些程序,比如文件系統(tǒng)、socket等,我們上層的應(yīng)用就是使用他們的紙張,
去組合出ABC等一些特性的上層程序,我們的”紙張“和A\B\C\D一樣,也在這個(gè)大循環(huán)內(nèi),我們的程序也可以通過一些共享手段與它們通訊/交換信息,
如下圖:
為了避免每個(gè)程序一直掛在那里影響循環(huán)后面的其他程序,通常來說都需要合理的分配每個(gè)程序執(zhí)行的最大時(shí)間,
如果沒執(zhí)行完,那就等下一個(gè)循環(huán)接著執(zhí)行(具體可以看看Linux時(shí)間片算法,這里不展開說了);假定每個(gè)程序最大執(zhí)行時(shí)間是1秒,
那么如圖所示的“循環(huán)”去除“等等”部分,轉(zhuǎn)一圈需要6秒;假設(shè)A執(zhí)行一次需要6秒,那么A就需要轉(zhuǎn)六圈才能完成一輪業(yè)務(wù),其他的同理。
在“紙張2”需要一直等待結(jié)果的情況下(就好比你致電10086詢問你的話費(fèi)余額,電話那頭告訴你等一下,你就一直拿著電話等她回答你),
我們假設(shè)A需要6秒,C需要10秒,那么“紙張2”完成自己的業(yè)務(wù)邏輯那么就需要先讓A轉(zhuǎn)完6圈,再去通知C,然后再等C轉(zhuǎn)完10圈,最后就是16圈,
那么在第17圈的時(shí)候“紙張2”才能拿到所有數(shù)據(jù)。
那么我有沒有辦法做到同時(shí)通知兩位,然后在等待呢?有:
“紙張2”開兩個(gè)線程(線程和進(jìn)程的關(guān)系這里不展開講,可以自行查閱,因?yàn)榫€程又是另一個(gè)“圈”的事兒了),一個(gè)通知A,一個(gè)通知C;
“紙張2”告訴A:“有結(jié)果了就放在門衛(wèi)大爺A那,我去拿”,然后緊接著通知C:“有結(jié)果了就放在門衛(wèi)大爺C那,我去拿”;
1號(hào)方案和2號(hào)方案其實(shí)在本質(zhì)上沒太大區(qū)別,因?yàn)樵趌inux下面實(shí)際上沒有真正的線程,無非就是多了一個(gè)循環(huán)的事兒,
即便多了真·線程的概念,實(shí)際上運(yùn)行起來也差不多,反正也是需要切出去再回來的;
“紙張2”除了需要維護(hù)一個(gè)不斷查詢門衛(wèi)大爺?shù)倪壿嬐?,不需要多余在維護(hù)什么,那么這個(gè)轉(zhuǎn)圈的事兒,就可以變換成如下的邏輯:
“紙張2”分別告訴A和C,于是開始轉(zhuǎn)圈,第一圈的時(shí)候A和C都消耗了1秒,直到第6圈A完事兒了,第7圈的時(shí)候“紙張2”拿到了A的包裹;
第10圈的時(shí)候,C完事兒了,第11圈的時(shí)候“紙張2”拿到了C的包裹,程序結(jié)束;那么這個(gè)程序就執(zhí)行了11圈,比之前的17圈要快一些。
實(shí)際上這個(gè)邏輯就是linux select/poll的執(zhí)行邏輯,如果算上“紙張2”自己還需要查尋門衛(wèi)大爺?shù)臅r(shí)間的話,是不止17圈的,
因?yàn)槊恳蝗Α凹垙?”都需要去問大爺,這里只是做了一個(gè)忽略;如果假設(shè)詢問大爺需要消耗2圈的話,那么至少要在第19圈才可以拿到結(jié)果
還有沒有更快的方案呢?有:
讓A和C完事兒的時(shí)候告訴我一聲
這時(shí)候“紙張2”是不需要去詢問大爺,只需要在A、C完事兒通知到的時(shí)候去大爺那里取,這樣的時(shí)間消耗是最接近17圈的。
這種方式就是linux epoll的執(zhí)行方式,也是類似于kqueue或者IOCP的執(zhí)行方式,當(dāng)然系統(tǒng)不同,時(shí)間分配的方式也不同,
整體的執(zhí)行方式也稍微有所調(diào)整
學(xué)習(xí)中