天天看點

并發程式設計模型和通路控制

最為傳統的模型, 為每個request起個新的thread去執行, 以實作并發 

最大的問題是, 擴充問題, request很多的時候, 太多的thread會産生很大的排程耗費, 當然可以使用線程池來進行優化 

這種模型比較适合于cpu密集的request, 并設定近似于cpu核數的線程數, 達到并發計算的效果

還有就是并發粒度問題, 隻能在request級别進行并發

并發程式設計模型和通路控制

為了解決并發粒度問題, 思路是把一個request拆成n個stage, 每個stage都單獨一個fsm線程(有限狀态機), 并依賴中央scheduler進行統一協調 

當request到達的時候, 通過schdeuler依次發送event, 完成n個stage 

這樣解決擴充性問題, 因為是基于stage級别去并發, 是以就算有10000個request, 仍然隻需要5個stage線程 

再者, 并發粒度比較小, 對于request中不同的stage可以設定不同的并發度

當然這個方案問題很明顯, 耦合度和複雜度比較高, 不同類型的request都需要實作一系列的fsm線程, 并且scheduler的實作會比較複雜

并發程式設計模型和通路控制

對比現在說的比較多的event-drive模型, 和這個還是有差别的 

對于現在越來越多的web應用, 大部分應用都是i/o等待密集的request, 這種case使用多線程方式是非常低效的 

是以需要使用event-drive模型, reactor模式, 用一個線程去等待10000個request和用10000個線程去等待, 效果上沒有差別, 但是耗費上卻天壤之别 

當然這種基于單線程的reactor模式, 隻能節省i/o等待時間, 但對于cpu計算密集型的request因為是單線程, 是以就相當于串行執行 

沒看出seda和actor兩種模型的差别, 思路和方法基本一緻 

和event-based模型的關鍵不同, 就是去耦合和去中心化 

對于event-based, 需要scheduler知道所有的過程, 負責所有的event的發送和協調

但對于seda, 完全的去耦合, 對于任一個stage, 都是獨立的, 完全可重用的, stage之間完全通過event進行溝通

對于每個stage隻需要知道, 下級stage是誰(将資料發送給誰), 開發人員可以任意配置outgoing events, 進而形成workflow

對于stage級别的并發度, 通過設定thread pool就可以簡單的設定

并發程式設計模型和通路控制

當然問題是, 依賴event queue, 效率上有些問題, lmax disruptor就是來解決這個效率瓶頸的

這個問題經常會和上面的問題混合在一塊, 這樣不清晰 

任一種并發的程式設計模型都會有通路控制問題, 解決這個問題的方法其實也很簡單, 加鎖.

完全互斥鎖, 我做的時候, 你等着, 我做完, 你再做

簡單, 問題效率低

妥協一點, 各自做各自的, 隻在最終送出的時候, 去check目前的狀态是否已經變化, 如果已經變化那麼送出失敗 

根據最新的狀态, 處理邏輯後重新送出

另一種思路, 不加鎖, 各寫各的, 是以我們可以同時儲存相同資料的不同版本, 然後在client query的時候傳回所有的版本, 讓client自己去決定選取什麼版本 

nosql常常采用這種方式, 當然這個方法明顯加重了client的負擔

stm其實是綜合了mvcc和樂觀鎖機制, 比較典型的案例是, couchdb和clojure

用通俗的描述解釋一下, 

首先是mvcc, 大家都可以并發的随意的在自己的版本上修改資料, 當然你的資料别人是看不得的 

然後是樂觀鎖, 當你想要commit的時候, 這個時候是需要加樂觀鎖的, commit的過程其實就是将公共的reference指向你的版本

為什麼是transactional?  

因為你可以在你的版本上做很多改動, 但僅僅當reference切換成功後, 所有的更新會被可見 

如果reference切換失敗, 所有的更新都不會被可見 

通過簡單的機制就是避免的rollback等複雜的操作, 但是卻達到了和transaction同樣的效果

是以就算你的更新沒有成功或由于crash丢失了, 至少不會影響原來資料的一緻性

本文章摘自部落格園,原文釋出日期:2013-07-03

繼續閱讀