本文想從一個(gè)角度來(lái)讓大家認(rèn)識(shí)到回答一個(gè)問(wèn)題不能從表面或者感覺(jué)來(lái)判斷迅办,這是學(xué)習(xí)技術(shù)的大忌,很多新手如果剛開(kāi)始沒(méi)有一套學(xué)習(xí)方法章蚣,從各種碎片化信息去學(xué)習(xí)編程站欺,學(xué)習(xí)一段時(shí)間很容易進(jìn)入瓶頸期,所以我覺(jué)得有必要通過(guò)一些我平時(shí)思考過(guò)的問(wèn)題纤垂,踩過(guò)得坑矾策,來(lái)總結(jié)一下也方便自己日后鞏固,另一方面也想分享出來(lái)幫助需要的人洒忧,讓大家發(fā)現(xiàn)解決一個(gè)問(wèn)題只有知道背后越多的概念和設(shè)計(jì)蝴韭,思路才會(huì)越多够颠,才會(huì)懂得基礎(chǔ)的重要性熙侍,如果有幸能引發(fā)共鳴和思考,就非常幸運(yùn)了,當(dāng)然文中大部份的內(nèi)容都是我自己內(nèi)化過(guò)之后用淺顯的語(yǔ)言描述蛉抓,盡量讓更多的人能聽(tīng)懂庆尘,不會(huì)像很多博客復(fù)制粘貼,我覺(jué)得也沒(méi)有意義巷送,如果有哪些地方?jīng)]有講清楚驶忌,也歡迎大家交流補(bǔ)充。
首先從問(wèn)題當(dāng)中有幾個(gè)重要的關(guān)鍵詞笑跛,請(qǐng)求付魔,線程,多飞蹂,單几苍,快。我們一個(gè)一個(gè)來(lái)稍為補(bǔ)充一下:
1. 請(qǐng)求陈哑。在題目上下文里指的是客戶端發(fā)送多條HTTP請(qǐng)求到服務(wù)端妻坝。假設(shè)是發(fā)送到同一個(gè)服務(wù)器, 都是HTTP1.1 以上協(xié)議開(kāi)啟了多路復(fù)用的情況惊窖。那就是一條TCP鏈接刽宪。? ? ?
2. 線程。一般客戶端發(fā)送HTTP會(huì)啟一個(gè)單獨(dú)線程界酒,不在主線程渲染UI線程發(fā)起圣拄。? ?
3. 多。就是開(kāi)多個(gè)單獨(dú)線程分別去請(qǐng)求盾计。這里的多的目的是建立多個(gè)TCP還是多個(gè)操作系統(tǒng)的線程售担。? ??
4. 單。就是單獨(dú)一個(gè)操作系統(tǒng)中的線程署辉,自然TCP鏈接就是一個(gè)族铆。? ? ? ?
5. 快。從題目來(lái)看應(yīng)該是想更快接收到所有請(qǐng)求返回給上層業(yè)務(wù)處理哭尝,想讓CPU更快處理完哥攘?還是期望客戶端和服務(wù)器傳輸?shù)臅r(shí)間盡量快?
基于以上拆分出來(lái)的一些小問(wèn)題我們需要了解一些以下的幾個(gè)概念:
CPU計(jì)算密集型:邏輯很復(fù)雜材鹦,非常多的計(jì)算量逝淹,讓CPU幾乎跑滿了,還覺(jué)得達(dá)不到預(yù)期的速度桶唐。
I/O密集型:一直在等待一些外部設(shè)備的輸入或者輸出栅葡,一般速度更CPU關(guān)系不大,比如等待請(qǐng)求返回或者發(fā)送尤泽,等待硬盤(pán)的讀入或者寫(xiě)入等等欣簇。
一规脸、進(jìn)程/線程/協(xié)程
首先需要知道的概念就是進(jìn)程,進(jìn)程是操作系統(tǒng)調(diào)度資源的單位熊咽,怎么理解呢莫鸭?就是一種資源分配隔離機(jī)制,不同進(jìn)程當(dāng)然不能隨意互向修改對(duì)方資源横殴,本著公平的原則被因,由操作系統(tǒng)同一分配安排,當(dāng)然在任一刻的時(shí)候CPU只可能在執(zhí)行一個(gè)進(jìn)程中衫仑,不能同時(shí)在執(zhí)行兩個(gè)進(jìn)程梨与,一般我們開(kāi)發(fā)的App等都是一個(gè)進(jìn)程,那為什么可以同時(shí)聽(tīng)著別的音樂(lè)App的音樂(lè)文狱,看著另外一個(gè)App內(nèi)的文章呢蛋欣,不是同時(shí)進(jìn)行的嗎?
你以為你以為的同時(shí)就是同時(shí)嗎如贷?
你的大腦欺騙了你陷虎,只要CPU在多個(gè)進(jìn)程間切換的足夠快,每個(gè)進(jìn)程只給一點(diǎn)點(diǎn)時(shí)間杠袱,這個(gè)時(shí)間差小到你都感覺(jué)不到尚猿,你就覺(jué)得你的這個(gè)App一直是獨(dú)占了CPU,從沒(méi)離開(kāi)過(guò)你這個(gè)App楣富, 這個(gè)跟我們看視頻看動(dòng)畫(huà)一樣的道理凿掂,圖片切換的足夠快你就認(rèn)為那個(gè)是運(yùn)動(dòng)的動(dòng)態(tài)的,非一些離散的時(shí)間點(diǎn)拼湊的纹蝴。
接下來(lái)要說(shuō)的是線程庄萎,為什么要有線程這個(gè)概念,理論上操作系統(tǒng)的分時(shí)機(jī)制來(lái)快速切換多個(gè)進(jìn)程讓大家都有機(jī)會(huì)被CPU執(zhí)行已經(jīng)很完美了塘安,那就要從單個(gè)進(jìn)程內(nèi)部來(lái)看糠涛,大部分客戶端都會(huì)有一個(gè)主線程,然后耗時(shí)I_O等會(huì)放到其他線程兼犯,為什么這么做呢忍捡,其一渲染在一個(gè)線程來(lái)做簡(jiǎn)單,多線程會(huì)有數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題切黔,其二如果都在主線程如果某個(gè)I_O時(shí)間等待太長(zhǎng)一直占住就沒(méi)有機(jī)會(huì)給UI線程渲染就會(huì)體驗(yàn)比較差砸脊,所以大部分的情況跑在主線程UI線程會(huì)讓體驗(yàn)更好,這么來(lái)看在客戶端大部分情況多線程的目的是為了I/O的等待纬霞,當(dāng)然還有一種就是計(jì)算量太大開(kāi)個(gè)線程跑凌埂,是為了利用多核,我們這里暫且討論單核诗芜。
最后是協(xié)程瞳抓∶虢簦可能寫(xiě)客戶端的接觸協(xié)程應(yīng)該還比較少,Kotlin就有協(xié)程挨下,當(dāng)然很多語(yǔ)言都有協(xié)程,為什么又要搞出個(gè)這個(gè)東西呢脐湾,多線程還不能滿足嗎臭笆,多線程一般為了I/O的時(shí)候不阻塞當(dāng)前線程而開(kāi)啟了多個(gè)線程,但是線程的創(chuàng)建需要大概1M的資源秤掌,所以不能創(chuàng)建太多愁铺,其二多線程切換切換也有成本,需要保存上下文值闻鉴,所以協(xié)程就是一種更輕量的執(zhí)行任務(wù)的單元茵乱,協(xié)程和線程區(qū)別是:協(xié)程一般由編程語(yǔ)言的內(nèi)部實(shí)現(xiàn),由可以控制的調(diào)度器去控制切換孟岛,而線程雖然也可以控制當(dāng)前所在線程瓶竭,但是線程所屬的進(jìn)程由操作系統(tǒng)搶占式的切換。協(xié)程屬于輕量級(jí)線程渠羞,一般背后可以有1對(duì)1或者M(jìn)對(duì)N或者N對(duì)1的真實(shí)線程斤贰,不同語(yǔ)言實(shí)現(xiàn)不一樣。例如Python的協(xié)程是N對(duì)1次询,Golang的goroutine是M對(duì)N荧恍。
二、為什么要用多進(jìn)程/線程/協(xié)程
上面主要介紹了客戶端會(huì)使用多線程的情況屯吊,客戶端一般不會(huì)用到多進(jìn)程送巡,當(dāng)然也可以,多進(jìn)程的使用一般都為了利用多核去處理計(jì)算密集型盒卸,多核可以看成多臺(tái)計(jì)算機(jī)一起運(yùn)算骗爆,再說(shuō)說(shuō)服務(wù)端為什么要用多進(jìn)程或者多線程呢?如果是單進(jìn)程的話蔽介,我們寫(xiě)的服務(wù)端代碼放在服務(wù)器淮腾,當(dāng)有一個(gè)用戶請(qǐng)求進(jìn)來(lái)需要處理至少需要一個(gè)進(jìn)程,此時(shí)如果沒(méi)有處理完又來(lái)了一個(gè)用戶請(qǐng)求需要處理屉佳,此時(shí)如果還是單進(jìn)程就需要排隊(duì)了谷朝,那么這個(gè)并發(fā)量大的時(shí)候顯然我們是不能接受的,所以此時(shí)我們可以用單進(jìn)程的方式武花,但是用多線程每個(gè)用戶對(duì)應(yīng)一個(gè)線程去處理圆凰,我們上面知道線程是比較重的,而且開(kāi)的線程數(shù)量也有限因?yàn)橘Y源是有限的体箕,一般服務(wù)端會(huì)用線程池的方式prefork就是預(yù)先創(chuàng)建一堆線程专钉。所以只是一種不太好的方案挑童,多進(jìn)程的方案嘛也可以,跟上面一樣只是更加占用資源了跃须,prefork的方式是不錯(cuò)站叼,但是有個(gè)缺陷是并發(fā)量不大的時(shí)候也要預(yù)先創(chuàng)建一堆線程,俗話就是站著坑不拉屎菇民,而協(xié)程的創(chuàng)建幾乎不占用多少資源尽楔,可以隨時(shí)用隨時(shí)釋放,所以現(xiàn)在一些高性能web服務(wù)器都采用這種方案第练。就不舉例子了阔馋。
三、發(fā)送請(qǐng)求背后發(fā)生了什么
終于到了網(wǎng)絡(luò)部分娇掏,我們知道TCP鏈接的建立需要三次握手呕寝,其中一部分的原因就是因?yàn)樾枰p方共識(shí)大家初始化的id來(lái)標(biāo)識(shí)每次請(qǐng)求的id,什么是建立連接婴梧?拿一跟線兩頭連接上嗎下梢?當(dāng)然不是,連接的本質(zhì)是雙方各自初始化了一個(gè)socket的標(biāo)識(shí)符塞蹭,可以當(dāng)成一個(gè)文件怔球,以后這個(gè)連接發(fā)送過(guò)來(lái)的東西都存到這個(gè)里面,所以說(shuō)連接只是讓雙方確認(rèn)一下浮还,認(rèn)識(shí)到以后有數(shù)據(jù)進(jìn)來(lái)可以識(shí)別出來(lái)竟坛。發(fā)送請(qǐng)求其實(shí)就是先把要發(fā)送的數(shù)據(jù)存到一個(gè)緩沖區(qū),然后等待操作系統(tǒng)幫你一層層打包發(fā)送到網(wǎng)卡然后走網(wǎng)線出去而已钧舌,接受就是一個(gè)相反的過(guò)程本質(zhì)是一樣的担汤。所以我們打包的過(guò)程其實(shí)是很快的,接受也是洼冻,慢是慢在這個(gè)路程中崭歧,跟快遞一樣,所以就算開(kāi)了多個(gè)操作系統(tǒng)的線程并不會(huì)加快多少速度撞牢,開(kāi)多個(gè)TCP的鏈接呢率碾,理論上會(huì)快,跟多線程下載是一個(gè)原因屋彪,快的是你想占用服務(wù)器更多的帶寬所宰,對(duì)于服務(wù)端來(lái)說(shuō)帶寬一定,他需要公平的對(duì)待多條TCP鏈接畜挥,所以當(dāng)然你占用條數(shù)越多速度也相對(duì)會(huì)快一點(diǎn)仔粥,這里面還有很多細(xì)節(jié)會(huì)影響速度,客戶端的帶寬,客戶端的TCP鏈接數(shù)躯泰,TCP擁塞控制等等谭羔,讀者們可以去擴(kuò)展閱讀,本文能達(dá)到啟蒙或者有那么點(diǎn)意思就夠了麦向。
總結(jié):
經(jīng)過(guò)上面我們的分析我們可以發(fā)現(xiàn)瘟裸,回答一個(gè)問(wèn)題可能并沒(méi)有銀彈,需要有一個(gè)場(chǎng)景一個(gè)上下文诵竭,一個(gè)具體的業(yè)務(wù)中我們?cè)龠\(yùn)用這些基礎(chǔ)的知識(shí)去深入思考话告,來(lái)給我們程序設(shè)計(jì)或者調(diào)優(yōu)。所以說(shuō)努力一定就會(huì)成功嗎秀撇?如果遇到一定這個(gè)詞,我們需要的是多個(gè)角度向族,多個(gè)維度來(lái)去看待問(wèn)題呵燕,給出我們認(rèn)為的一種思路,而不是想當(dāng)然件相,感謝你認(rèn)真的讀完再扭,這是我第一次嘗試用自己的語(yǔ)言去解釋一些編程中的問(wèn)題,如果你覺(jué)得還不錯(cuò)或者哪里不太好的地方歡迎給我留言夜矗,也許就是你的留言才讓我堅(jiān)持下去?
如需轉(zhuǎn)載請(qǐng)聯(lián)系本人泛范,標(biāo)注轉(zhuǎn)發(fā)自原始地址。想要關(guān)注更多博主文章請(qǐng)關(guān)注公眾號(hào)紊撕,也可以訪問(wèn)博客地址:Blog[1]
References
[1]?Blog:?koofrank.com