寫(xiě)在前面的
存在即合理, 不管什么事, 都是有原因有理由有前提的, 所以在談?wù)撝拔覀兿纫鞔_一些東西
1. 服務(wù)器端使用多線程的必要條件是多核, 且物理核的計(jì)算能力總和>>服務(wù)器程序的計(jì)算量. 如果不滿足上述條件, 應(yīng)該先考慮硬件配置問(wèn)題.
2. 為什么要用多線程? 是為了充分利用多核增加計(jì)算量, 增大計(jì)算量的目的是什么? 支持更為復(fù)雜的玩法. 這里有很重要的信息, 那就是"支持更為復(fù)雜的玩法"是目的, 而"增加計(jì)算量"是達(dá)到目的的手段; 前者是策劃域問(wèn)題, 而后者是程序域問(wèn)題. 一個(gè)需求和實(shí)現(xiàn)的結(jié)合點(diǎn).
3. 當(dāng)計(jì)算量達(dá)到時(shí), 其他短板開(kāi)始起作用, 也就是說(shuō)不要去追求所謂最好利用計(jì)算量之方法, 轉(zhuǎn)而追求計(jì)算量不要成為最短板即可.
這片文章是在有"支持更為復(fù)雜的玩法"這樣的需求, 且當(dāng)前存在"計(jì)算量目前是最短板", 且"由于沒(méi)有充分利用多線程發(fā)揮機(jī)器多核計(jì)算量即計(jì)算量有較多剩余"的情況下的一些探討和心得.
申明: 本文并不想得出任何"我這個(gè)就對(duì)!"的方案, 只是將我在實(shí)踐中遇到的問(wèn)題, 和解決問(wèn)題或是看到的一些思路寫(xiě)出來(lái), 期望能引發(fā)大家的探討:) 相比之下文中提高的方案只不過(guò)是一個(gè)example罷了
一個(gè)地圖一個(gè)線程的方案
關(guān)于在游戲服務(wù)器中使用多線程, 有一個(gè)常常被提到的方案是: 一個(gè)地圖一個(gè)線程, 這個(gè)方案的一個(gè)推廣就是平行子系統(tǒng)中每個(gè)子系統(tǒng)對(duì)應(yīng)一個(gè)線程. 個(gè)人認(rèn)為這個(gè)方案還是值得商榷的. 下面來(lái)說(shuō)明一下我的看法.
根據(jù)一些講解系統(tǒng)的名著中的定義: 在問(wèn)題域的世界中, 交互聯(lián)系格外緊密的一群實(shí)體之間形成了一個(gè)系統(tǒng). 一個(gè)子系統(tǒng)之所以被劃分成為一個(gè)子系統(tǒng), 是因?yàn)樵谧酉到y(tǒng)中的成員的交互和聯(lián)系頻率要遠(yuǎn)遠(yuǎn)大于和系統(tǒng)外實(shí)體的交互和聯(lián)系, 這是一個(gè)很重要的結(jié)論. 我們可以從這里得到一個(gè)推論: 劃分良好的子系統(tǒng)內(nèi)部交互非常緊密, 和外部實(shí)體交互相對(duì)較弱. 很巧的是我們?cè)谠O(shè)計(jì)中往往會(huì)把這里的子系統(tǒng)劃分為程序中的一個(gè)功能模塊, 模塊也有這樣的性質(zhì), 模塊中的交互要遠(yuǎn)遠(yuǎn)大于模塊間的交互.
有了上述理論的支持, 再來(lái)看看為什么說(shuō)"一個(gè)地圖一個(gè)線程"這樣的方案不可取? 一個(gè)地圖是一個(gè)子系統(tǒng), 地圖間的交互是非常小的, 的確是這樣, 可能就是在不同地圖中移動(dòng)時(shí)等不頻繁事件. 這里可以分析一下"地圖綁定線程"這個(gè)想法的依據(jù):
1. 增大計(jì)算量. 在系統(tǒng)中引入多線程, 利用多核, 加大服務(wù)器端的計(jì)算能力(提供更多的游戲邏輯處理), 這樣就可以在一個(gè)服務(wù)器上容納更多的玩家.
2. 防止犯錯(cuò). 避開(kāi)多線程的鎖的問(wèn)題, 把交互性強(qiáng)的部分分在同一個(gè)線程中, 這樣的話在大部分情況下避免鎖, 只有在很少的跨地圖交互時(shí)才使用同步方法來(lái)保證[避免鎖可以防止鎖的競(jìng)爭(zhēng), 更重要的是在實(shí)際編程中解放程序員, 因?yàn)闀r(shí)刻考慮多線程問(wèn)題太痛苦了, 犯錯(cuò)不可避免]. 放在這里就可以發(fā)現(xiàn)按地圖劃分是一個(gè)很好的劃分線程的方法.
該方法的問(wèn)題在于首先它不一定很發(fā)揮出多核的效果, 因?yàn)橛螒蛑腥瞬惶珪?huì)是均勻的分布在多個(gè)地圖上. 這樣就會(huì)產(chǎn)生有的線程都忙不過(guò)來(lái)了[地圖人很多], 有的線程很閑[地圖人很少], 問(wèn)題的本質(zhì)是這種使用線程的方法限制了每個(gè)地圖的可使用計(jì)算量[無(wú)形中的一個(gè)假設(shè)是: 單個(gè)核的計(jì)算量是足夠的, 一旦現(xiàn)實(shí)情況不符, 服務(wù)器端就會(huì)掉幀], 也就是說(shuō)對(duì)每個(gè)地圖來(lái)說(shuō), 還是單線程的, 并沒(méi)有達(dá)到想要的"增大計(jì)算量"的目地. 其次, 系統(tǒng)的穩(wěn)定性差, 因?yàn)橹灰幸粋(gè)線程core掉, 后果可想而知. 再次, 暫時(shí)不談前兩個(gè)問(wèn)題, 那么在計(jì)算量得到充分滿足的條件下, 根據(jù)"短板效應(yīng)", 你的系統(tǒng)的能力又會(huì)開(kāi)始受到內(nèi)存, 網(wǎng)絡(luò)等其他因素的影響, 這時(shí)你可能會(huì)想要在其他機(jī)器上橫向擴(kuò)展系統(tǒng), 你必須單獨(dú)設(shè)計(jì)這一部分, 多線程體系本來(lái)就有"我能發(fā)揮機(jī)器的計(jì)算能力, 計(jì)算量從此不是問(wèn)題"這樣的傾向, 但是它的一個(gè)不可回避的問(wèn)題就是, 多線程的執(zhí)行流共享程序進(jìn)程空間, 它必須在同一臺(tái)主機(jī)上. 分布式的橫向擴(kuò)展對(duì)多線程來(lái)說(shuō)是另一個(gè)課題, 它從來(lái)沒(méi)想過(guò)這個(gè), 你得自己去實(shí)現(xiàn).
其實(shí)根據(jù)設(shè)計(jì)的特點(diǎn), 這里還不如只用多進(jìn)程來(lái)代替多線程, 因?yàn)?
1. 多進(jìn)程同樣發(fā)揮多核的計(jì)算能力, 至少在"地圖綁定線程"這個(gè)問(wèn)題上不弱于多線程, 但也不會(huì)強(qiáng), 因?yàn)橐彩艿絾尉程地圖的影響.
2. 多進(jìn)程穩(wěn)定性好, 一個(gè)地圖進(jìn)程core了, 其他地圖不受影響.
3. 使用socket進(jìn)行地圖[進(jìn)程]間通信手段, 天然支持分布式部署, 在系統(tǒng)需要橫向擴(kuò)展時(shí), 不用做任何的額外工作.
總的來(lái)說(shuō), 這種平行子系統(tǒng)中一個(gè)子系統(tǒng)對(duì)應(yīng)一個(gè)線程的方法看似使用的多線程, 但是本質(zhì)上還是單線程的設(shè)計(jì)思路, 所以不是一種很好的方案, 但是這個(gè)方法有它的可行性和合理性, 因?yàn)樗拇_是分?jǐn)偭擞?jì)算量, 而且不用過(guò)度考慮鎖的問(wèn)題, 所以它還是有很重要的參考價(jià)值.
去現(xiàn)實(shí)世界中尋找答案
另一個(gè)想法是, 游戲世界中的每一個(gè)active obj都有一個(gè)線程+一個(gè)消息對(duì)列. 這個(gè)想法看起來(lái)不現(xiàn)實(shí), 但是它是出于對(duì)現(xiàn)實(shí)世界中可響應(yīng)的實(shí)體的最好模擬. 我記得剛工作的時(shí)候, 帶我的人最后也是my friend的--徐曉剛同學(xué)就告訴我, 游戲中的實(shí)體一定要和現(xiàn)實(shí)生活中的類(lèi)似實(shí)體有對(duì)應(yīng), 現(xiàn)實(shí)實(shí)體是怎么樣的, 你游戲中的實(shí)體就做成什么樣, 當(dāng)時(shí)我聽(tīng)了覺(jué)得不對(duì)啊, 不是要抽象么什么什么的, 典型的書(shū)看多了, 但是現(xiàn)在看來(lái)確實(shí)是這樣的, 游戲中的實(shí)體都是源于生活的, 要想做一個(gè)好的模擬, 還是得觀察和研究現(xiàn)實(shí)中的實(shí)體才行.
比如現(xiàn)實(shí)生活中確實(shí)就是這樣, 可以有反應(yīng)的實(shí)體都是時(shí)刻在接受著外界的信息(消息), 然后它的反應(yīng)其實(shí)就是在處理這個(gè)信息[在它自己的線程里處理這個(gè)消息]. 比如說(shuō), 你丟一個(gè)骨頭給一個(gè)小狗, 就相當(dāng)于給它發(fā)了個(gè)消息[你有吃的了], 小狗就會(huì)處理這個(gè)消息[啃骨頭]. 再比如說(shuō), 媽媽告訴小孩, 去把垃圾倒了, 小孩聽(tīng)到這個(gè)消息, 就會(huì)去倒垃圾. 可以看出來(lái), 這都是 可執(zhí)行體+消息 的結(jié)構(gòu). 當(dāng)然, 媽媽也可以告訴小孩, 你去把垃圾倒了, 然后再去商店買(mǎi)一點(diǎn)鹽. 小孩收到這兩個(gè)消息的時(shí)候, 就會(huì)記下來(lái), 先去到垃圾, 然后去買(mǎi)鹽, 這個(gè)其實(shí)相當(dāng)于小孩有一個(gè)記錄消息的隊(duì)列, 他會(huì)依次處理隊(duì)列中的消息.
所以這里我個(gè)人得出的結(jié)論是在多線程系統(tǒng)中:
1. 系統(tǒng)本身是內(nèi)聚封閉的, 它不能直接去查看不是自己內(nèi)部的數(shù)據(jù), 方法等, 別人也不能直接來(lái)查看它的相關(guān)信息, 系統(tǒng)只負(fù)責(zé)維護(hù)自己的狀態(tài).
2. 如果需要交互, 系統(tǒng)間應(yīng)該顯式的走消息機(jī)制.
在現(xiàn)實(shí)生活中找到有反應(yīng)的實(shí)體并觀察它們的行為后, 就可以尋找它們的共同點(diǎn), 這個(gè)過(guò)程就是抽象, 抽象并不是打開(kāi)編輯器, 想著自己要寫(xiě)代碼來(lái)抽象這個(gè)問(wèn)題呀, 而是在現(xiàn)實(shí)中找到對(duì)應(yīng)并歸納共性的過(guò)程. 這里看到, 可以獨(dú)立的反應(yīng)的實(shí)體具有兩個(gè)性質(zhì):
1. 可以獨(dú)立行為---也就是說(shuō)他們是active的. 一個(gè)npc是一個(gè)系統(tǒng), 一個(gè)player也是一個(gè)系統(tǒng).
2. 有一個(gè)消息隊(duì)列---他們可以處理隊(duì)列中的消息.
映射到程序中, 可以獨(dú)立行為就單獨(dú)給它分一根線程唄, 消息隊(duì)列不是問(wèn)題, 每個(gè)線程的唯一任務(wù)就是處理消息隊(duì)列中的消息. 但是如果能這么簡(jiǎn)單就不叫"源于生活高于生活了". 這樣的弊端一看就看出來(lái)了, 游戲中每個(gè)有反應(yīng)的實(shí)體一個(gè)線程不現(xiàn)實(shí), 就拿我知道的來(lái)說(shuō), 5000個(gè)npc和2000個(gè)玩家吧, 都是有反應(yīng)的實(shí)體, 一共啟動(dòng)5000+2000=7000根線程, 顯然不現(xiàn)實(shí), 開(kāi)銷(xiāo)上受不了.
但是這個(gè)抽象的確很不錯(cuò), 很符合現(xiàn)實(shí), 所以我不想放棄它. 那么還是要從現(xiàn)實(shí)世界中學(xué)習(xí)如何"降低開(kāi)銷(xiāo)", 類(lèi)比一下, 比如一個(gè)食堂的老板, 他雇傭了一個(gè)洗白菜工, 一個(gè)洗芹菜工, 一個(gè)切白菜工, 一個(gè)切芹菜工, 一個(gè)炒白菜工, 一個(gè)炒芹菜工, 這樣的配置不能說(shuō)錯(cuò), 沒(méi)問(wèn)題可以工作, 不過(guò)過(guò)了幾天老板就發(fā)現(xiàn)了, 開(kāi)銷(xiāo)太大了, 每個(gè)月掙的不如花的多, 而且還有人員有大把空閑時(shí)間. 于是他讓洗菜的不管白菜芹菜一起洗, 切菜和炒菜的人也一樣. 這樣就剩下3個(gè)人了, 過(guò)了幾天后老板又開(kāi)掉了洗菜的人, 讓切菜的人兼上洗菜的活, 這樣就只剩下兩個(gè)人的人力開(kāi)銷(xiāo).
類(lèi)比到計(jì)算機(jī)中, 我們創(chuàng)建的線程就是雇傭工人, 工人不是免費(fèi)的, 是有人力成本的, 線程也一樣, 它不是免費(fèi)的, 是有開(kāi)銷(xiāo)的. 所以你創(chuàng)建不了太多的線程, 就和你請(qǐng)不起太多的員工是一個(gè)道理. 在現(xiàn)實(shí)生活中通過(guò)讓一個(gè)員工干更多的活, 來(lái)達(dá)到降低成本的方法, 在這里也是適用的.
首先講一個(gè)不易區(qū)分的地方, 就是"能動(dòng)"和"動(dòng)"的區(qū)別. 英文active和actor, 一個(gè)是形容詞, 一個(gè)是動(dòng)詞. 這個(gè)很重要, 因?yàn)槲覀兊南到y(tǒng)中, active object很多, 但是actor是需要控制的, 不可能太多. actor其實(shí)直接對(duì)應(yīng)于一個(gè)線程, 而active object對(duì)應(yīng)于諸多的可反應(yīng)的實(shí)體. 比如npc, player等等.我們需要用actor來(lái)驅(qū)動(dòng)active object, 沒(méi)有被驅(qū)動(dòng)的active object是靜止的, 只有用actor才能賦予它活力. 所以我們可以寫(xiě)下如下的代碼.
可反應(yīng)的實(shí)體的定義
class activeobj {
msgqueue mq;
void Active() {
processMsg(mq.PopFront());
}
void RecvWork(msg) {
mq.push(msg);
}
}
創(chuàng)建多個(gè)實(shí)體
activeobj objs[34];
actor驅(qū)動(dòng)實(shí)體
void actor() {
while (true) {
foreach(obj in objs) {
obj.Active()
}
}
}
這里, 每個(gè)activeobj都有自己的消息隊(duì)列, 你可以使用RecvWork向activeobj發(fā)送消息, 而這個(gè)activeobj被actor激活的時(shí)候就會(huì)去處理自己消息隊(duì)列中的消息, 這樣整個(gè)世界中的obj都具有活力了, 下面代碼表示在多線程世界中上述代碼的形式, 也就是說(shuō), 存在多個(gè)actor
可反應(yīng)的實(shí)體的定義
class activeobj {
msgqueue mq;
lock mc;
void Active() {
mc.Lock();
msg m = mq.PopFront();
mc.Unlock();
processMsg(m);
}
void RecvWork(msg) {
mc.Lock();
mq.push(msg);
mc.Unlock();
}
}
創(chuàng)建多個(gè)實(shí)體
activeobj objs[100];
actor驅(qū)動(dòng)實(shí)體
void actor(int i) {
while (true) {
for (j = i * 25; j < (i + 1) * 25; ++j) {
objs[j].Active()
}
}
}
創(chuàng)建actor
createActor(actor, 0)
createActor(actor, 1)
createActor(actor, 2)
createActor(actor, 3)
上面演示的策略是創(chuàng)建4個(gè)actor, 每個(gè)actor負(fù)責(zé)驅(qū)動(dòng)固定的25個(gè)obj, 每個(gè)activeobj有自己的消息隊(duì)列鎖, 這樣就可以保證消息隊(duì)列的線程安全. 這樣的好處是比較簡(jiǎn)單, 但是這個(gè)做法的問(wèn)題也有很多:
1. 消息的時(shí)序不對(duì), 比如, 我先給obj[3]發(fā)了一個(gè)消息, 然后給obj[1]發(fā)了一個(gè)消息, 那么根據(jù)上面的邏輯, obj[1]的消息很可能會(huì)先于obj[3]被處理. 如果這兩個(gè)消息之間先后沒(méi)有關(guān)系還好, 但是一旦有邏輯上的先后關(guān)系, 這樣處理就是錯(cuò)誤的了. 問(wèn)題的根源是: 處理消息總是按照obj的排列順序, 而不是按照收到消息的前后順序.
2. 這種方法還是落入了不能平分負(fù)載的問(wèn)題中, 因?yàn)榭赡躠ctor1管理的obj群不活躍, 而actor2管理的obj群異常的活躍. 就會(huì)出現(xiàn)一個(gè)線程特別忙, 另一個(gè)線程閑置的狀態(tài), 在往下說(shuō), 就是有可能發(fā)生一個(gè)CPU 100%了, 另一個(gè)CPU20%, 但是游戲掉幀, 就是因?yàn)闆](méi)有均攤計(jì)算量. 這也可能成為一個(gè)優(yōu)點(diǎn), 因?yàn)椴粫?huì)出現(xiàn)兩個(gè)線程中同時(shí)處理一個(gè)obj的情況, 也就是說(shuō)對(duì)obj自身來(lái)說(shuō)是在單線程環(huán)境中的. 具有單線程的安全性.
改進(jìn)方案
所以我們必須繼續(xù)思考這個(gè)問(wèn)題, 上面的第二點(diǎn)好解決, 可以這樣
創(chuàng)建多個(gè)實(shí)體 …
lock oc;
activeobj GetObj() {
oc.Lock();
foreach(obj in objs) {
if (!obj.mq.empty()) {
objs.Remove(obj);
oc.Unlock();
return obj;
}
}
oc.Unlock();
return nullobj;
}
void actor() {
while (true) {
obj = GetObject();
if (obj != nullobj) {
obj.Active();
} else {
yield();
}
}
}
經(jīng)過(guò)上述改動(dòng)以后, 可以看到, 只要有一個(gè)線程空閑下來(lái), 它就會(huì)去查詢是否有obj存在msg等待被處理. 如果沒(méi)有, 線程就短暫的休息一下. 這樣的話, 計(jì)算量基本上可以實(shí)現(xiàn)均攤到多個(gè)線程上. 但是這種方案有明顯的一個(gè)鎖競(jìng)爭(zhēng)的hot point, 就是GetObj函數(shù)中. 這里不適合用重量級(jí)的鎖, 而且這里的競(jìng)爭(zhēng)會(huì)隨actor的數(shù)量上升而變得更激烈.
上面的方案算是一個(gè)解決計(jì)算分?jǐn)偟霓k法, 但是沒(méi)有解決消息的時(shí)序問(wèn)題. 話說(shuō)基于消息的系統(tǒng)成堆成堆的, 比如說(shuō)日常中用到的windows系統(tǒng)就是基于消息驅(qū)動(dòng)的, 可以從它身上學(xué)到一些東西. Windows的控件可以看做是active object, 因?yàn)樗梢詫?duì)你的操作進(jìn)行反應(yīng), 比如你點(diǎn)擊它, 拖動(dòng)它等等, 這些都是發(fā)消息來(lái)實(shí)現(xiàn)的. 這里就是關(guān)鍵, 消息到底發(fā)給誰(shuí)了
1. 從表象上來(lái)看, 因?yàn)槭强丶䦟?duì)消息產(chǎn)生了反應(yīng), 我們從感覺(jué)上就認(rèn)為消息是發(fā)給控件的, 控件上可能有一個(gè)消息隊(duì)列, 同時(shí)我們知道, 其實(shí)控件只是active obj, 它是由actor ui線程來(lái)驅(qū)動(dòng)的.
2. 但是我們從資料上可以輕松找到其實(shí)控件都沒(méi)有消息隊(duì)列, 消息隊(duì)列是和ui線程綁定在一起的:) 也就是說(shuō)一個(gè)actor一個(gè)隊(duì)列, 而不是說(shuō)一個(gè)active obj一個(gè)隊(duì)列.
我認(rèn)為這windows這個(gè)消息模型是很有借鑒價(jià)值的, 應(yīng)用到我們的設(shè)計(jì)中就是
可反應(yīng)的實(shí)體的定義
class activeobj {
void Active(msg m) {
processMsg(m);
}
}
actor的定義
class actor {
msgqueue mq;
void Loop() {
msg m = mq.popfront();
obj = FindObj(m.t) else goto yield();
obj.Active(m);
}
}
上述機(jī)制看上去又再一次的落入任務(wù)和線程綁定的模式中, 我們還是要再次研究, 一個(gè)游戲世界中, 只有一個(gè)時(shí)間軸, 如果說(shuō)每個(gè)消息都有一個(gè)時(shí)間戳的話, 那么它們應(yīng)該是"先來(lái)后到"的關(guān)系, 因?yàn)槲覀円WC消息有序. 所以, 我們不能使用一個(gè)actor一個(gè)隊(duì)列的方式, 消息隊(duì)列必須是全局的, 然后又幾個(gè)actor監(jiān)視這個(gè)隊(duì)列, 一旦有消息進(jìn)入的話, 就有一個(gè)actor會(huì)取到它并去處理. 為了驗(yàn)證我們的想法, 我在現(xiàn)實(shí)中找到了這樣實(shí)現(xiàn)的模型, 就是目前windows下最高效的IO模型完成端口, 完成端口的完成隊(duì)列就相當(dāng)于這里的消息隊(duì)列, 而actor就是線程池, 每個(gè)actor都企圖從完成隊(duì)列中取一個(gè)任務(wù)來(lái)執(zhí)行. 這和我們的想法是非常類(lèi)似的. 所以再次修改代碼:
可反應(yīng)的實(shí)體的定義
class activeobj {
void Active(msg m) {
processMsg(m);
}
}
定義一個(gè)全局的消息隊(duì)列
msgqueue mq;
mqlocal ml;
actor的定義
class actor {
void Loop() {
ml.lock();
msg m = mq.poptop() else goto yield();
ml.unlock();
obj = FIndObj(m);
obj->Active(m);
}
}
上面就是一個(gè)可選的方案, 首先它分?jǐn)偭擞?jì)算量, 計(jì)算量在msgqueue中存著, 真正的處理邏輯是在activeobj中, 而actor驅(qū)動(dòng)activeobj來(lái)執(zhí)行msgqueue中的消息的處理. 從設(shè)計(jì)的角度來(lái)說(shuō), 做到了職責(zé)分離. 而前面的設(shè)計(jì)中, msgqueue不是和activeobj綁定在一起, 就是和actor綁定在一起, 所以總是有一些問(wèn)題, 看來(lái)還是大牛們說(shuō)的對(duì), 每個(gè)概念都單獨(dú)表示, 然后通過(guò)組合把它們聯(lián)系起來(lái).
上面的這個(gè)方案好么? 首先, 我們的游戲中, 時(shí)序是非常重要的, 我們總是期望先到的消息能夠先處理[因?yàn)樗犬a(chǎn)生], 所以必須有一個(gè)消息排隊(duì)的地方, 所以有一個(gè)全局隊(duì)列是很重要的.其次就是, 我們不期望發(fā)生一個(gè)線程很忙, 另一個(gè)很閑的情況, 使用全局msg隊(duì)列就可以達(dá)到收集任務(wù), 管理任務(wù), 分配任務(wù)的效果, 上面這種模式就可以在大部分情況下平均分?jǐn)傆?jì)算量.
總結(jié)
上面簡(jiǎn)單介紹了一個(gè)多線程方案, 我并不是直接的給出了答案, 而是記錄了我在思考這個(gè)問(wèn)題的過(guò)程中遇到的各種問(wèn)題, 和如何解決這些問(wèn)題的思路(只能說(shuō)是思路:), 我認(rèn)為其中最重要的部分就是:
1. 抽象應(yīng)該是源于生活的, 不是空想的.
2. 對(duì)系統(tǒng)的看法: 一個(gè)系統(tǒng)之所以被劃分成為一個(gè)系統(tǒng), 是因?yàn)樵谙到y(tǒng)中的成員的交互和聯(lián)系頻率要遠(yuǎn)遠(yuǎn)大于和系統(tǒng)外實(shí)體的交互和聯(lián)系.
3. 各個(gè)系統(tǒng)不直接交互, 如果必須交互, 請(qǐng)一定走M(jìn)SG
4. MSG應(yīng)該維持時(shí)序性, 我這里使用的是全局MSG隊(duì)列來(lái)保證時(shí)序性.
在文中討論到最后的方案中, 任然存在著一些問(wèn)題, 比如說(shuō)在這種方案下, 全局的MSG隊(duì)列就是唯一一個(gè)全局且高競(jìng)爭(zhēng)數(shù)據(jù), 這里鎖的互斥是高發(fā)的, 所以在選擇鎖的時(shí)候需要斟酌等等, 希望能有所幫助, 歡迎大家提出更好的解決方案, 建設(shè)性的交流總是有益的:)