如何在c語(yǔ)言中使用惰性計(jì)算替代并發(fā)程序的鎖機(jī)制

上一節(jié)描述了如何使用c語(yǔ)言的函數(shù)指針實(shí)現(xiàn)一個(gè)結(jié)構(gòu)體接口秉犹,從而實(shí)現(xiàn)了類似于面向?qū)ο蟮姆庋b機(jī)制玄帕,多態(tài)性只是其中一個(gè)方便的地方,更為重要的是译秦,它使用消息機(jī)制代替了值的更改,所以我們可以不再使用傳統(tǒng)的獲取狀態(tài)然后賦值的方式來(lái)實(shí)現(xiàn)一個(gè)系統(tǒng),在正常情況下诀浪,我們獲得了抽象機(jī)制棋返,這好像已經(jīng)足夠強(qiáng)大了,但是還不夠雷猪,在當(dāng)今網(wǎng)絡(luò)服務(wù)的興起情況下睛竣,由于并發(fā)系統(tǒng)的流行,我們將看到這樣的抽象方法論的真正威力求摇。
讀者可以復(fù)習(xí)一下之前的文章射沟,因?yàn)檫@個(gè)模型也是使用之前的c語(yǔ)言語(yǔ)法來(lái)實(shí)現(xiàn)。

“這就創(chuàng)建了一種簡(jiǎn)單的時(shí)態(tài)邏輯与境,將“事實(shí)集合”顯現(xiàn)為世界線的層層堆椦楹唬”

--《Alan Kay談OO和FP》

首先我們需要一個(gè)并發(fā)執(zhí)行的函數(shù)。

typedef struct shareOBJ shareOBJ;
struct shareOBJ{
  int id;
  ...
};
void pthread(void * obj){
   shareOBJ * object = (shareOBJ*)obj;
  message * client_message = obj->client_message;
  obj->exec(client_message);//獲取客戶端命令并執(zhí)行
}
int main(){
  shareOBJ * obj = new_ShareOBJ();//初始化內(nèi)存空間
  while(1){
     obj->accept();//阻塞等待服務(wù)器消息
     pthread_create(NULL,NULL,pthread, (void*)obj)摔刁;//開啟新線程處理
  }
}

我們可以看到挥转,現(xiàn)在的邏輯處理是完全封閉的,具體的業(yè)務(wù)是什么共屈?不知道绑谣,內(nèi)部的邏輯是什么,也不知道拗引,我們只知道借宵,現(xiàn)在每到來(lái)一個(gè)請(qǐng)求,我們就開啟一個(gè)線程去處理矾削,其中客戶端消息全部存儲(chǔ)到我們的shareOBJ這個(gè)類型的結(jié)構(gòu)體里面壤玫。
我們暫且按照具體的情況來(lái)處理,假如現(xiàn)在我們?cè)趕hareOBJ結(jié)構(gòu)體內(nèi)部維護(hù)了這樣一組數(shù)據(jù):客戶端的消息:message,賬戶總額:cash哼凯,客戶余額表:table.

{
  ...
  message * msg;
  float cash;
  table * table;
  ...
}

通常的情況下欲间,假如來(lái)了一個(gè)客戶要取錢,我們可以這樣處理,將客戶的余額按照他的表中的賬戶進(jìn)行計(jì)算断部,然后將表賦值為更改之后的值括改,由于表中的數(shù)據(jù)在同一時(shí)間內(nèi),一個(gè)客戶只能執(zhí)行對(duì)應(yīng)自己賬戶的一次操作家坎,所以我們的數(shù)據(jù)不會(huì)出現(xiàn)任何問(wèn)題。

//接口1:將客戶的賬戶余額按照客戶指定的存取方式進(jìn)行核算
computeClient(table*tab,message*msg){
  tab->data[msg->clientID] = tab->data[msg->clientID] + msg->putgetCash;
}

但是我們同時(shí)還要計(jì)算賬戶的總額吝梅,這個(gè)時(shí)候如果再進(jìn)行更改和賦值虱疏,就會(huì)出現(xiàn)大問(wèn)題了,因?yàn)榭蛻舸嫒∵壿嬒履玫降目傆囝~數(shù)據(jù)是不一樣的苏携,我們具體來(lái)看做瞪。

//接口2:計(jì)算銀行的總額
computeBank(float * cash,message * msg){
  *cash = *cash + msg->putgetCash;
}

現(xiàn)在的執(zhí)行邏輯是這樣:
cash0進(jìn)入客戶1,cash0進(jìn)入客戶2,cash0進(jìn)入客戶3......
客戶1存錢或者取錢装蓬,得到了一份拷貝cash1,
客戶2存錢或者取錢著拭,得到了一份拷貝cash2,
客戶3存錢或者取錢,得到了一份拷貝cash3.
我們知道cash1和cash2還有cash3是cash0在不同的客戶邏輯下得到的存取數(shù)目牍帚,而我們每一次得到的數(shù)據(jù)都是并發(fā)執(zhí)行的儡遮,這樣的總額將會(huì)出現(xiàn)奇怪的變化。
假如按照上面的邏輯來(lái)計(jì)算暗赶,我們下一次得到的必定是cash1,現(xiàn)在我們的第四個(gè)客戶來(lái)了鄙币,他是我們的銀行老板,使用cash1結(jié)算后的銀行總額來(lái)查帳本,卻發(fā)現(xiàn)蹂随,明明有3個(gè)人來(lái)存了錢十嘿,但是現(xiàn)金庫(kù)里和賬戶點(diǎn)算出來(lái)只有客戶1一個(gè)人存了錢,導(dǎo)致我們的現(xiàn)金還足余岳锁,銀行賬本上已經(jīng)沒(méi)錢了绩衷,難道是因?yàn)榭蛻?和3的錢還沒(méi)有打到賬上嗎?仔細(xì)想想激率,我們無(wú)論哪一個(gè)人先存錢咳燕,賬本都是按照之前的那個(gè)沒(méi)有更新的賬本在計(jì)算,并且將任意一個(gè)人的計(jì)算結(jié)果放到了總的余額中柱搜,所以導(dǎo)致了這樣的情況迟郎。
出現(xiàn)問(wèn)題的根本原因就是我們采用了并發(fā)的策略,而不是排隊(duì)的方式來(lái)處理我們的計(jì)算過(guò)程聪蘸,每一個(gè)上下文引用后的變量都是分離的宪肖,最終又把在時(shí)間中丟失一部分狀態(tài)后的這些操作直接安裝到原來(lái)的時(shí)間線上,我們完全可以使用排隊(duì)的方式處理這個(gè)問(wèn)題健爬,從一個(gè)客戶進(jìn)入處理流程控乾,再到結(jié)束辦理,我們?cè)賵?zhí)行等待處理下一個(gè)客戶的業(yè)務(wù)娜遵,但是這樣可能會(huì)頻繁切換上下文蜕衡,導(dǎo)致的性能開銷也是及其昂貴的,甚至是無(wú)法忍受的设拟,因?yàn)橐粋€(gè)業(yè)務(wù)流程中慨仿,最消耗時(shí)間的并不是對(duì)余額進(jìn)行計(jì)算,而是對(duì)客戶數(shù)據(jù)的計(jì)算纳胧,而我們的客戶數(shù)據(jù)是可以并發(fā)執(zhí)行沒(méi)有問(wèn)題的镰吆,所以一個(gè)可行的解決方案就是將客戶數(shù)據(jù)的業(yè)務(wù)推入并發(fā)流程中,而余額等一系列共享變量的計(jì)算使用循環(huán)等待的方式解決跑慕,但是這仍然具有很大的性能消耗万皿,有沒(méi)有一種辦法既可以不用頻繁切換上下文摧找,又可以把丟失的那部分未經(jīng)計(jì)算的狀態(tài)找回來(lái)呢?當(dāng)然是有的牢硅,那就是我們的惰性計(jì)算蹬耘。

惰性計(jì)算的好處

惰性計(jì)算其實(shí)理解起來(lái)很簡(jiǎn)單,就是將原本時(shí)間線上本應(yīng)立即計(jì)算的一個(gè)對(duì)象或者狀態(tài)延遲一段時(shí)間再進(jìn)行計(jì)算减余,這往往在函數(shù)式編程中利用它來(lái)實(shí)現(xiàn)無(wú)窮數(shù)列這樣的數(shù)據(jù)結(jié)構(gòu)综苔,又叫做流,在當(dāng)年那個(gè)計(jì)算機(jī)性能極度缺乏的情況下佳励,這是一種魔法般的武器休里,不過(guò)到了今天,這樣的抽象方法論更多地用在并發(fā)環(huán)境下方便的地操作數(shù)據(jù)同步和解決鎖機(jī)制帶來(lái)的問(wèn)題赃承。

那么實(shí)現(xiàn)的關(guān)鍵步驟在于妙黍,我們將結(jié)構(gòu)體shareOBJ中的固定值 float cash 更改為可以存儲(chǔ)當(dāng)前值的歷史的一個(gè)棧,我們客戶端的更改操作可以設(shè)置為向棧取出一個(gè)當(dāng)前時(shí)間幀上的真實(shí)數(shù)據(jù)瞧剖,并向其中存入一個(gè)更改后的歷史值拭嫁,也就是說(shuō),無(wú)論我們的時(shí)間幀有多么小抓于,取到的數(shù)據(jù)都是在時(shí)間上同步后的數(shù)據(jù)做粤,因?yàn)槲覀兊臈⑺械母挠涗浂急4嫦聛?lái)了,現(xiàn)在關(guān)鍵的地方到了捉撮,我們從哪里尋找這樣的棧呢怕品?它必須要安裝所有的歷史記錄才可以,好像我們走到了一個(gè)想當(dāng)然的地步巾遭,這時(shí)候我們的惰性計(jì)算出場(chǎng)了肉康,我們完全可以不必提前得到一個(gè)無(wú)窮大的棧,而只需要在用到下一個(gè)椬粕幔空間的時(shí)候我們?cè)偃?gòu)造它吼和,聽起來(lái)好像理所當(dāng)然,我們用到的下一個(gè)椘锼兀空間肯定是構(gòu)造出來(lái)的呀炫乓,可是總會(huì)用完這個(gè)棧,很多人可能會(huì)想到一個(gè)辦法献丑,將這塊內(nèi)存作為緩沖區(qū)末捣,然后棧滿的時(shí)候就可以持久化,其實(shí)這是一個(gè)辦法创橄,但是還有更優(yōu)雅的解決方法塔粒,因?yàn)槲覀冎魂P(guān)心最終的余額,所以這些實(shí)時(shí)的計(jì)算過(guò)程可以另啟一個(gè)余額計(jì)算函數(shù)筐摘,每隔特定的時(shí)間幀或者滿足棧滿的觸發(fā)條件就對(duì)余額進(jìn)行核算,并且將棧清空,等待下一輪的訪問(wèn)高峰咖熟,因?yàn)槲覀兊臈?nèi)元素假如為滿圃酵,是無(wú)法再添加元素的,這時(shí)候可以返回失敗條件馍管,但是我們的余額計(jì)算函數(shù)速度是很快的郭赐,幾乎不會(huì)占用多少時(shí)間,反而是我們的客戶數(shù)據(jù)的計(jì)算還有日志存儲(chǔ)占用了太多的時(shí)間确沸,這個(gè)時(shí)候我們可以將消息記錄中的所有的總余額數(shù)據(jù)全部寫入磁盤進(jìn)行持久化捌锭,客戶端的操作什么時(shí)候可以返回成功條件呢?當(dāng)我們的余額計(jì)算時(shí)間片輪轉(zhuǎn)一次就返回一次成功消息罗捎,因?yàn)檩嗈D(zhuǎn)一次代表我們已經(jīng)處理了一輪時(shí)間剛好滿足的訪問(wèn)操作观谦,只要滿足任意一個(gè)條件我們就可以使用余額計(jì)算函數(shù)返回客戶端的操作消息。

看起來(lái)我們的計(jì)算函數(shù)成功了桨菜,因?yàn)樗挠?jì)算不再是模擬現(xiàn)實(shí)世界的時(shí)間線豁状,我們的用戶操作并不會(huì)立即得到計(jì)算結(jié)果,而是在我們?cè)O(shè)計(jì)者已經(jīng)規(guī)定的條件下才得到結(jié)果倒得,比如棧滿或者時(shí)間片輪轉(zhuǎn)的時(shí)候泻红,這就是一種解耦,將真實(shí)世界的時(shí)間線解耦到棧中霞掺,只要我們擁有完整的消息數(shù)據(jù)谊路,我們完全可以選擇任意時(shí)刻計(jì)算得到我們想要的結(jié)果,這也是函數(shù)式編程的思想菩彬,引用透明性缠劝,我們不需要模擬現(xiàn)實(shí)世界的時(shí)間走向,可以延遲到任意的時(shí)間去計(jì)算也不會(huì)產(chǎn)生改變挤巡,這便是時(shí)態(tài)邏輯剩彬,某些事實(shí)在任意時(shí)間都成立,無(wú)論等一千年還是一萬(wàn)年去把存取值的歷史棧取出來(lái)計(jì)算矿卑,我們最終得到的值仍然是一樣的喉恋,如果在并發(fā)中訪問(wèn)固定的變量和狀態(tài)變化的話,我們將會(huì)得到很可怕的結(jié)果母廷,因?yàn)槟承┲翟跁r(shí)間中消失了轻黑!

{

  stack * cash;
  
}
stack.setValue();//實(shí)際上此處的setValue已經(jīng)代替賦值操作
stack.time();//time 函數(shù)會(huì)計(jì)算當(dāng)前的時(shí)延,超過(guò)響應(yīng)時(shí)間就將進(jìn)行計(jì)算琴昆,并清空棧
stack.push();//push會(huì)將新的消息傳入棧
stack.compute();//計(jì)算函數(shù)會(huì)立即鎖定棧氓鄙,并計(jì)算所有的棧元素相加得到一個(gè)穩(wěn)定計(jì)算后的值,然后清空棧
stack.toNULL();//toNULL立即釋放棧的空間业舍,并解除鎖定抖拦,接收客戶端請(qǐng)求升酣。

看起來(lái)我們的操作好像顯得很復(fù)雜,但是實(shí)際上這樣的設(shè)計(jì)完美地避開了不穩(wěn)定狀態(tài)帶來(lái)的并發(fā)問(wèn)題态罪,并且我們只在執(zhí)行緩沖更換操作的時(shí)候通過(guò)內(nèi)部狀態(tài)的機(jī)制給定了一把不完全是鎖的鎖噩茄,因?yàn)樗臓顟B(tài)完全由對(duì)象本身來(lái)決定,而不是外部手動(dòng)去操作复颈,自然不會(huì)存在競(jìng)爭(zhēng)條件和死鎖等問(wèn)題绩聘,并且它的速度和安全性都遠(yuǎn)遠(yuǎn)高于使用鎖機(jī)制,當(dāng)我們?cè)趐ush的時(shí)候耗啦,我們的對(duì)象會(huì)自己內(nèi)省是否在進(jìn)行計(jì)算操作凿菩,否則就返回棧滿條件,外部邏輯是執(zhí)行循環(huán)等待還是直接返回錯(cuò)誤消息完全取決于外部的系統(tǒng)條件帜讲,push更好的方式甚至可以是內(nèi)部循環(huán)等待衅谷,外部的操作是無(wú)法觀察到當(dāng)前線程引用stack的push操作到底發(fā)生了什么,所以我們的并發(fā)系統(tǒng)成功運(yùn)行了舒帮,當(dāng)然這個(gè)設(shè)計(jì)還只是作者的一點(diǎn)思考会喝。
下面是具體的邏輯代碼

setValue(){
  if(cash.time()==0.2 || cash.state == LOCK){
     stack.compute()
     stack.unlock()
  }
  else{
    stack.push()
  }
}
computeBank(stack * cash,message * msg){
  cash.setValue(msg->putgetCash);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市玩郊,隨后出現(xiàn)的幾起案子肢执,更是在濱河造成了極大的恐慌,老刑警劉巖译红,帶你破解...
    沈念sama閱讀 212,657評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件预茄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡侦厚,警方通過(guò)查閱死者的電腦和手機(jī)耻陕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,662評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刨沦,“玉大人诗宣,你說(shuō)我怎么就攤上這事∠胱纾” “怎么了召庞?”我有些...
    開封第一講書人閱讀 158,143評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)来破。 經(jīng)常有香客問(wèn)我篮灼,道長(zhǎng),這世上最難降的妖魔是什么徘禁? 我笑而不...
    開封第一講書人閱讀 56,732評(píng)論 1 284
  • 正文 為了忘掉前任诅诱,我火速辦了婚禮,結(jié)果婚禮上送朱,老公的妹妹穿的比我還像新娘娘荡。我一直安慰自己干旁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,837評(píng)論 6 386
  • 文/花漫 我一把揭開白布它改。 她就那樣靜靜地躺著疤孕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪央拖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,036評(píng)論 1 291
  • 那天鹉戚,我揣著相機(jī)與錄音鲜戒,去河邊找鬼。 笑死抹凳,一個(gè)胖子當(dāng)著我的面吹牛遏餐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赢底,決...
    沈念sama閱讀 39,126評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼失都,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了幸冻?” 一聲冷哼從身側(cè)響起粹庞,我...
    開封第一講書人閱讀 37,868評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洽损,沒(méi)想到半個(gè)月后庞溜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,315評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碑定,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,641評(píng)論 2 327
  • 正文 我和宋清朗相戀三年流码,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片延刘。...
    茶點(diǎn)故事閱讀 38,773評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漫试,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碘赖,到底是詐尸還是另有隱情驾荣,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布崖疤,位于F島的核電站秘车,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劫哼。R本人自食惡果不足惜叮趴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望权烧。 院中可真熱鬧眯亦,春花似錦伤溉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,859評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宫静,卻和暖如春走净,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孤里。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工伏伯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捌袜。 一個(gè)月前我還...
    沈念sama閱讀 46,584評(píng)論 2 362
  • 正文 我出身青樓说搅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親虏等。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弄唧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,676評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容