并發(fā)
通常情況下會有很多用戶同時使用你的應(yīng)用,并且你也希望應(yīng)用能夠及時響應(yīng)用戶的請求觉痛。那么你就需要使用某種方法去處理并發(fā)問題役衡,大部分的 web服務(wù)器已經(jīng)默認實現(xiàn)了。但是當(dāng)你要擴大規(guī)模的時候薪棒,你就要盡可能的使用高效的并發(fā)模型映挂。
不同的并發(fā)類型
處理并發(fā)問題,有多種途徑:多線程盗尸,多線程和事件循環(huán)柑船,其中每一種都有自己的使用場景,優(yōu)點及缺點泼各。通過本文你會了解到它們的不同點和使用場景鞍时。
多進程(Unicorn)
是用多進程模型是比較容易實現(xiàn)并發(fā)的,主進程自身forks出多個工作進程扣蜻,工作進程是用于實際處理請求的逆巍,而主進程用于管理工作進程。
每個工作進程在內(nèi)存中有完整的代碼基莽使。這導(dǎo)致多進程模型很容易內(nèi)存吃緊并且很難擴充到大規(guī)模架構(gòu)上锐极。
多進程小結(jié)
場景
一個非 ruby 的例子就是眾所周知的Chrome 瀏覽器.
它使用多進程并發(fā)給每個標(biāo)簽頁分配一個進程,這樣就不會因為一個標(biāo)簽頁掛掉的導(dǎo)致整個瀏覽器停止工作芳肌。并且可以隔離開發(fā)單個標(biāo)簽頁灵再。
優(yōu)點
非常容易實現(xiàn),避免了多線程的安全問題亿笤,每個工作進程掛掉并不影響整個系統(tǒng)的其他部分翎迁。
缺點
每個進程會加載整個代碼庫到內(nèi)存當(dāng)中去。這會使得內(nèi)存變得吃緊净薛,因此不適合大規(guī)模并發(fā)請求汪榔。
多線程(Puma)
多線程模型,是通過在一個進程中運行多個線程來讓一個進程可以同時去處理多個請求肃拜。
與多進程不同, 所有的線程運行在一個進程中痴腌,這代表著它們中間是可以共享例如全局變量這樣的數(shù)據(jù)的雌团。因此每個線程只會額外使用少量的內(nèi)存。
全局解釋鎖(Global Interpreter Lock)
在 MRI中使用線程會遇到全局解釋鎖(GIL)士聪。GIL被是用于鎖定所有 ruby 代碼的執(zhí)行的辱姨,盡管我們的線程看上去是并行執(zhí)行的雨涛,但實際上一個時間內(nèi)只有一個線程是激活的懦胞。
IO 操作不受 GIL的限制,當(dāng)你等待執(zhí)行數(shù)據(jù)庫的返回結(jié)果時蚯根,是不會被鎖定的胀糜,其他的線程也是可以同時執(zhí)行同樣的 IO操作的,如果你在線程中使用哈暇嗨В或者數(shù)組去處理大量的數(shù)學(xué)計算的話,那么你只能利用一個核心括堤。如果你使用 MRI 在大部分場景下還行需要使用多進程模型碌秸,去充分利用機器資源,或者你也可以使用 Rubinius或 jRuby悄窃,
這種沒有 GIL 的 ruby 實現(xiàn)讥电。
線程安全
如果你使用多線程模型,那么就必須十分小心的操作線程中的共享數(shù)據(jù)轧抗。你可以通過使用 互斥鎖 在操作共享數(shù)據(jù)前鎖定它恩敌,這樣就可以確保其他線程不會在你已經(jīng)修改了數(shù)據(jù)的情況下,還使用過期的數(shù)據(jù)横媚。
多線程小總結(jié)
場景
這是一種折衷方案纠炮,被用于很多請求量少的普通 web 應(yīng)用中
優(yōu)點
相比于多進程模型,可以使用較少的內(nèi)存分唾。
缺點
你要確保你的代碼是線程安全的抗碰,如果線程出現(xiàn)了崩潰,那么它很有可能讓你的整個進程一起崩潰掉绽乔。GIL會鎖定除 IO之外的所有操作
事件循環(huán)(Thin)
事件循環(huán)是用于,當(dāng)你需要執(zhí)行很多并發(fā) IO操作的場景碳褒。這個模型本身不會強制的讓多個請求在同時執(zhí)行折砸,而是通過高效的方式處理并發(fā)看疗。
下面 你會看到非常簡單的使用 Ruby寫成的事件循環(huán)示例,循環(huán)從事件隊列中取出事件處理睦授,如果隊列為空两芳,他會休眠然后繼續(xù)查看隊列中是否有新的事件
loop do
if event_queue.any?
handle_event(event_queue.pop)
else
sleep 0.1
end
end
在圖中,我們能夠清晰的了解到事件循環(huán)是如何與操作系統(tǒng)隊列和內(nèi)存交互的
步驟
- 操作系統(tǒng)一直監(jiān)聽網(wǎng)絡(luò)和磁盤何時可用去枷。
- 當(dāng)操作系統(tǒng)看到 I/O 可用后, 它就會將事件添加到隊列中去竖螃。
- 隊列中存儲這若干,待事件循環(huán)處理的事件腻格。
- 事件循環(huán)用于處理事件菜职,并使用在內(nèi)存中存儲與連接相關(guān)的元數(shù)據(jù),也可以再次直接發(fā)送新的事件到事件隊列中去愁茁。
- 如果想要進行 I/O 操作鹅很,事件循環(huán)會告訴操作系統(tǒng),它想繼續(xù)那個特定的 I/O操作菠齿。操作系統(tǒng)繼續(xù)監(jiān)聽網(wǎng)絡(luò)和磁盤绳匀,并且當(dāng)I/O可用后再次添加事件
事件循環(huán)小結(jié)
用例
有很多并發(fā)連接的場景戈钢,例如 Slack, Chrome notifications
優(yōu)點
每個連接幾乎沒有多少內(nèi)存開銷殉了,可處理大量的并行連接
缺點
事件循環(huán)不是一個容易理解的模型。為防止隊列爆增隔箍,處理批次的大小必須小并且可預(yù)測鞍恢。
究竟該使用哪一種模型?
希望這篇文章能夠更好的讓你理解不同的并發(fā)模型蟆炊,雖然其中有些主題難以理解涩搓,但是掌握這些會讓你有工具去實驗并且能用正確的組織構(gòu)建你的應(yīng)用。
總結(jié)
大部分應(yīng)用適合使用多線程模型充边,Ruby/Rails 生態(tài)圈 看起來正在(緩慢)向這個方面發(fā)展浇冰。
如果你的應(yīng)用是高并發(fā)長連接的肘习,那么事件循環(huán)很容你幫你實現(xiàn)漂佩。
如果你的應(yīng)用不是高訪問量的網(wǎng)站器赞,那么使用多進程的方式就以及足夠好了墓拜。
當(dāng)然,還有一種可能是在多進程嵌套多線程爽锥,在多線程中再使用事件循環(huán)臣樱。