第二单元作业——电梯模拟总结
一、設(shè)計(jì)策略
1. 單部先來(lái)先服務(wù)電梯
第一次作業(yè)采用了最基本的生產(chǎn)者-消費(fèi)者模型,電梯請(qǐng)求是模型中的商品,將控制器作為存儲(chǔ)請(qǐng)求的倉(cāng)庫(kù),主線程作為生產(chǎn)者向倉(cāng)庫(kù)存放請(qǐng)求,電梯作為消費(fèi)者從倉(cāng)庫(kù)取出請(qǐng)求并處理。先來(lái)先服務(wù)的調(diào)度策略中,電梯一次只會(huì)處理一個(gè)請(qǐng)求,因此可將請(qǐng)求作為一個(gè)操作的原子。控制器儲(chǔ)存請(qǐng)求使用隊(duì)列結(jié)構(gòu),電梯每次取出隊(duì)列首位的請(qǐng)求執(zhí)行。
?
2. 單部可捎帶電梯
第二次作業(yè)沿用第一次作業(yè)的結(jié)構(gòu),區(qū)別在于為了實(shí)現(xiàn)可捎帶,電梯需要同時(shí)處理多條請(qǐng)求,因此不能再以一整個(gè)請(qǐng)求作為操作原子,而需要破拆成更小的任務(wù)。
我設(shè)立的電梯的原子操作分為乘客進(jìn)入、乘客退出、上移一層和下移一層,電梯在取出一條請(qǐng)求時(shí)將請(qǐng)求拆分成一個(gè)原子操作的序列,之后順序執(zhí)行隊(duì)列中的操作,即可完成請(qǐng)求。捎帶任務(wù)時(shí),由于捎帶的條件是請(qǐng)求與電梯運(yùn)行方向相同且乘客上下的樓層都在電梯的運(yùn)行方向上,被捎帶的請(qǐng)求的原子操作序列可以和電梯當(dāng)前的操作序列融合,實(shí)現(xiàn)多個(gè)任務(wù)共同執(zhí)行。
優(yōu)化方面嘗試實(shí)現(xiàn)look算法。當(dāng)主進(jìn)程試圖將一個(gè)新請(qǐng)求放入控制器時(shí),首先判斷該請(qǐng)求能否立即被捎帶,若可以,則直接加入電梯的操作隊(duì)列中,否則放入控制器的存儲(chǔ)鏈表中。控制器的存儲(chǔ)鏈表分為兩條,一條儲(chǔ)存上行任務(wù),一條儲(chǔ)存下行鏈表,同時(shí)記錄上行鏈表中起始樓層最低和下行鏈表中起始樓層最高的請(qǐng)求。當(dāng)電梯執(zhí)行完當(dāng)前的操作序列,準(zhǔn)備從控制器中取出一個(gè)新任務(wù)時(shí),電梯將從這兩個(gè)最遠(yuǎn)端的任務(wù)中選取起始樓層較近的一個(gè)執(zhí)行。這個(gè)算法的目的是使電梯在一次上下行中能夠盡量多的進(jìn)行捎帶,選取最遠(yuǎn)端的請(qǐng)求能夠?qū)⑼较蛏纤械娜蝿?wù)全部捎帶。
?
3. 三部停靠樓層不同的,有負(fù)載上限的電梯
第三次作業(yè)與第二次作業(yè)的區(qū)別在于:1)多部電梯。需要考慮如何給三個(gè)電梯分配任務(wù)。2)電梯停靠樓層不同。一個(gè)請(qǐng)求可能不能由一部電梯單獨(dú)完成,不能以請(qǐng)求為單位向電梯分配任務(wù),需要進(jìn)行拆分。3)有負(fù)載上限。電梯可能不能將所有可捎帶的請(qǐng)求全部捎帶,需要進(jìn)行選擇。
為了沿用第二次作業(yè)中的結(jié)構(gòu),我在第三次作業(yè)中設(shè)計(jì)了一個(gè)主控制器和每個(gè)電梯各自的控制器。主控制器負(fù)責(zé)對(duì)請(qǐng)求進(jìn)行預(yù)處理和將處理后的請(qǐng)求分配給每個(gè)電梯的控制器,請(qǐng)求的預(yù)處理即為此請(qǐng)求規(guī)劃路線,包括是否換乘、在哪層換乘、乘坐哪部電梯。為簡(jiǎn)化問(wèn)題,我通過(guò)靜態(tài)方法規(guī)劃路徑,與電梯當(dāng)前所在的樓層與狀態(tài)無(wú)關(guān),可直達(dá)的請(qǐng)求必選擇直達(dá)的方式,不得不轉(zhuǎn)乘的任務(wù)選擇固定的樓層進(jìn)行換乘。主線程經(jīng)過(guò)主控制器的判斷后,將處理的請(qǐng)求分配給對(duì)應(yīng)的電梯的控制器,每個(gè)電梯處理各自的控制器中的任務(wù)的方式和第二次作業(yè)相近。區(qū)別在于電梯間對(duì)于換乘的通信和超過(guò)負(fù)載的處理方式。
在電梯間的協(xié)調(diào)運(yùn)作上,我讓電梯在當(dāng)前沒(méi)有能夠執(zhí)行的請(qǐng)求時(shí),向未來(lái)將出現(xiàn)請(qǐng)求,即來(lái)自其他電梯的換乘請(qǐng)求的樓層移動(dòng),但當(dāng)移動(dòng)過(guò)程中出現(xiàn)可立即執(zhí)行的請(qǐng)求時(shí),移動(dòng)需要被打斷,去響應(yīng)需要立即執(zhí)行的請(qǐng)求。為實(shí)現(xiàn)這一功能,需要將未來(lái)將換乘本電梯的任務(wù)存儲(chǔ)在本電梯控制器的另一個(gè)隊(duì)列中,當(dāng)電梯完成一個(gè)請(qǐng)求(執(zhí)行乘客退出的原子操作)時(shí),檢查該乘客是否需要換乘,若需要,則將下一步需要乘坐的電梯的控制器中該乘客對(duì)應(yīng)的存儲(chǔ)于未來(lái)任務(wù)序列中的任務(wù)移動(dòng)到可立即執(zhí)行的任務(wù)序列中。而當(dāng)一個(gè)電梯沒(méi)有可立即執(zhí)行的任務(wù)時(shí),向未來(lái)任務(wù)隊(duì)列中首項(xiàng)的任務(wù)的換乘樓層移動(dòng)。
對(duì)于超過(guò)負(fù)載的情況,我使用拒載的方式。當(dāng)電梯執(zhí)行到某一條乘客進(jìn)入的指令時(shí),若電梯負(fù)載已滿,則不讓乘客進(jìn)入,同時(shí)清除操作序列中該乘客退出的指令,然后將該乘客對(duì)應(yīng)的請(qǐng)求重新加回到控制器的可立即執(zhí)行隊(duì)列中。
優(yōu)化方面,在任務(wù)分配上,由于使用了靜態(tài)分配的方式,性能上已經(jīng)受到限制,只能通過(guò)調(diào)整優(yōu)先級(jí)調(diào)整各個(gè)電梯的工作量,從概率上能夠提升一點(diǎn)效率。
?
二、 度量分析
1. 單部先來(lái)先服務(wù)電梯
電梯類的run函數(shù)里由于包含了處理一個(gè)請(qǐng)求的流程(移動(dòng)、開(kāi)關(guān)門、乘客上下),有面向過(guò)程的成分在,因此方法較長(zhǎng),復(fù)雜度較高。這個(gè)問(wèn)題在三次作業(yè)中都有體現(xiàn)。
時(shí)序圖是一個(gè)基本的生產(chǎn)者消費(fèi)者模型。
?
2.?單部可捎帶電梯
復(fù)雜度較高的幾個(gè)方法中,控制器的putRequest是主線程向控制器存放請(qǐng)求的函數(shù),里面包含了維護(hù)look算法的上行下行請(qǐng)求鏈表的步驟。getRequest是電梯向控制器索取主任務(wù)的方法,同時(shí)包含了確定主任務(wù)后檢索所有可捎帶任務(wù)的步驟。電梯的pickQuest是將一個(gè)請(qǐng)求分解成原子操作序列的方法。Run是電梯每次從原子序列中取出一條指令并執(zhí)行的方法。這些方法控制分枝較多,代碼較長(zhǎng),同時(shí)覆蓋了多個(gè)功能,不太符合面對(duì)對(duì)象的思想,我沒(méi)有將這些函數(shù)進(jìn)行拆分的主要原因是我不知道該給新的函數(shù)起什么名字。。。
時(shí)序圖比起上一次作業(yè)多出了一條由倉(cāng)庫(kù)主動(dòng)(主線程運(yùn)行)將任務(wù)塞給電梯的路線
?
3.?三部停靠樓層不同的,有負(fù)載上限的電梯
第三次作業(yè)的復(fù)雜度由于大部分沿用第二次作業(yè)的結(jié)構(gòu),沒(méi)有太大的變化,多出一個(gè)復(fù)雜度較高函數(shù)是EditedRequest的構(gòu)造函數(shù),根據(jù)乘客的起止樓層規(guī)劃線路,控制分枝很多。
在第三次作業(yè)中存在主線程修改電梯對(duì)象、電梯線程修改自身、電梯線程修改另一個(gè)電梯對(duì)象的過(guò)程,線程之間的互斥關(guān)系比較復(fù)雜,最終交上的作業(yè)中也依然存在著一些時(shí)序上混亂的問(wèn)題,沒(méi)能取得理想的成績(jī)。
從soild原則上分析,由于這一次沒(méi)有使用類的繼承,主要從單一功能原則和開(kāi)放封閉原則分析。在這一次的控制器設(shè)計(jì)上,我按照管理范圍將控制器分為了三個(gè)層次:管理所有電梯,向各電梯分配任務(wù)的主控制器、存儲(chǔ)主控制器分配的任務(wù),決定執(zhí)行策略的字控制器和將任務(wù)轉(zhuǎn)換成原子操作,然后執(zhí)行的電梯本身,從描述上就可以看出,每個(gè)層級(jí)依然承擔(dān)著多項(xiàng)任務(wù),這也導(dǎo)致我的程序中出現(xiàn)長(zhǎng)度大,判斷復(fù)雜的方法,在單一責(zé)任的角度看,程序的分層還不夠抽象,導(dǎo)致部分功能混雜在一起。
我第三次作業(yè)中的每一部電梯和它的子控制器之間的交互移植自第二次作業(yè)。但為了實(shí)現(xiàn)讓電梯拒載,讓子控制器存儲(chǔ)將來(lái)?yè)Q乘的任務(wù),讓電梯在完成一個(gè)任務(wù)時(shí)同時(shí)其他電梯它完成了該任務(wù)等功能,還需要對(duì)第二次作業(yè)進(jìn)行一些修改,其中大部分的改動(dòng)是通過(guò)新增成員變量和方法實(shí)現(xiàn)的,但一些方法中邏輯上的控制和判斷依然需要修改,那些復(fù)雜度高的長(zhǎng)函數(shù)是尤其的重災(zāi)區(qū)。這違反開(kāi)放封閉原則,我也切身的體會(huì)到了修改完這些方法之后debug是多么痛苦,這也側(cè)面的證明了單一責(zé)任原則有多么重要。
?
三、bug分析
在這三次作業(yè)中我所遇到的bug主要是線程安全問(wèn)題的的bug。由于濫用synchronized導(dǎo)致的死鎖和由于synchronized了錯(cuò)誤的對(duì)象導(dǎo)致的線程不安全。
我發(fā)現(xiàn)java中一個(gè)線程可以通過(guò)synchronized嵌套來(lái)占據(jù)多個(gè)對(duì)象的鎖,但wait只能釋放其中一個(gè)鎖,而不能將全部的鎖釋放。在我的電梯中,由于主線程(producer)可能調(diào)用控制器(tray)中的putRequest函數(shù)時(shí)可能直接將請(qǐng)求(product)交給電梯(customer),電梯線程在調(diào)用控制器的getRequest時(shí)需要先掌握自身的鎖,getRequest函數(shù)需要得到控制器的鎖,若控制器中沒(méi)有指令,電梯線程就會(huì)放開(kāi)控制器的鎖進(jìn)入阻塞,但電梯線程沒(méi)有釋放電梯自身的鎖,因此當(dāng)主線程向?qū)⒁粋€(gè)請(qǐng)求直接交給電梯時(shí),就會(huì)發(fā)生死鎖。
對(duì)于這種情況我沒(méi)有好的解決辦法,只能盡量調(diào)整同步塊的范圍,使其盡量不發(fā)生嵌套,但這又導(dǎo)致了另一種問(wèn)題。我的控制器中有這樣的一類表達(dá):if (elev.canPickUp(request)) {elev.pickRequest();}。其中elev的兩個(gè)函數(shù)是電梯類中的兩個(gè)同步函數(shù)。可見(jiàn)兩個(gè)函數(shù)邏輯上是應(yīng)該緊接著連續(xù)執(zhí)行的,但中間稍微將elev的鎖放開(kāi)了一下,就導(dǎo)致可能有其他線程搶到鎖,插在中間執(zhí)行,修改了elev的成員變量,導(dǎo)致電梯捎帶任務(wù)時(shí)出現(xiàn)錯(cuò)誤。
我認(rèn)為會(huì)發(fā)生這樣的矛盾的根本原因是我沒(méi)有處理好方法的調(diào)用關(guān)系。我剛開(kāi)始設(shè)計(jì)程序結(jié)構(gòu)時(shí)是因?yàn)閾?dān)心cpu時(shí)間過(guò)高而放棄了讓電梯輪詢檢查控制器中有沒(méi)有新增的函數(shù),但后續(xù)了解到,控制合理的輪詢不會(huì)增加太多的cpu時(shí)間,若我將主線程嚴(yán)格限制在只能訪問(wèn)到控制器,由電梯輪詢檢查控制器是否改變,若改變則調(diào)整任務(wù)序列,也就不會(huì)出現(xiàn)上述的同步塊嵌套等問(wèn)題。
?
四、檢測(cè)bug的策略
第一、二次作業(yè)由于涉及的情況相對(duì)比較簡(jiǎn)單,可以人工構(gòu)造不同情況的數(shù)據(jù),如上下行的轉(zhuǎn)向,等待后的再啟動(dòng)空轉(zhuǎn)時(shí)的捎帶等等在自己編程中總結(jié)出來(lái)的需要進(jìn)行處理的情況。進(jìn)一步的檢測(cè)我通過(guò)自己寫的一個(gè)隨機(jī)測(cè)試數(shù)據(jù)生成器和定時(shí)輸入器進(jìn)行隨機(jī)測(cè)試,將多個(gè)人的測(cè)試結(jié)果放在一起對(duì)比可以發(fā)現(xiàn)一些不正常的行為,猜測(cè)其原因并針對(duì)這一點(diǎn)構(gòu)造數(shù)據(jù),可以發(fā)現(xiàn)一些性能上的問(wèn)題。
第三次作業(yè)由于簡(jiǎn)單的輸出難以進(jìn)行壓力測(cè)試,復(fù)雜的輸出有難以判斷正誤,只使用隨機(jī)數(shù)據(jù)進(jìn)行檢測(cè),用一個(gè)模擬程序判斷輸出的結(jié)果正確或出現(xiàn)哪種錯(cuò)誤。通過(guò)調(diào)整隨機(jī)數(shù)據(jù)生成的策略可以適當(dāng)轉(zhuǎn)移測(cè)試的重點(diǎn)。
沒(méi)有想到能比較好的檢查線程安全的方法。。。
?
五、心得體會(huì)
多線程編程是在問(wèn)題模型具有明顯的并發(fā)性時(shí)選擇的編程方法,這次實(shí)驗(yàn)的電梯就是一個(gè)典型的例子。進(jìn)行多線程編程首先要線程的劃分,確定模型的結(jié)構(gòu),這次實(shí)驗(yàn)中,我將每一部電梯作為一個(gè)進(jìn)程,輸入作為一個(gè)進(jìn)程,我也見(jiàn)到有的同學(xué)為每一名乘客都建立了一條線程。確立進(jìn)程后需要考慮的是進(jìn)程間的隔離與通信,隔離是為了保證線程安全,互不干擾,通信則是線程間協(xié)作的需要。在線程安全上,我總結(jié)出的教訓(xùn)是,最好不要讓一個(gè)線程能夠直接修改另一個(gè)線程的對(duì)象,在生產(chǎn)者-消費(fèi)者模型中,生產(chǎn)者線程和消費(fèi)者線程是通過(guò)倉(cāng)庫(kù)這一緩沖區(qū)作為通信的中介,線程對(duì)象的每一個(gè)動(dòng)作都應(yīng)該是主動(dòng)的索取,而不是被動(dòng)的接收。這樣的設(shè)計(jì)可以保證線程對(duì)象不會(huì)被其他線程修改,不用考慮自身對(duì)象的同步性。緩沖區(qū)的設(shè)計(jì)需要注意每一個(gè)同步函數(shù)應(yīng)該只被一類線程訪問(wèn),put函數(shù)只能被生產(chǎn)者線程訪問(wèn),get函數(shù)只能被消費(fèi)者線程訪問(wèn),這可以簡(jiǎn)化設(shè)計(jì)線程安全時(shí)需要考慮的情況,防止復(fù)雜的情況發(fā)生。有時(shí)線程之間的通信可能經(jīng)過(guò)多個(gè)緩沖區(qū),可能發(fā)生死鎖,這時(shí)可以使用動(dòng)態(tài)加鎖等方法,避免死鎖發(fā)生。
轉(zhuǎn)載于:https://www.cnblogs.com/jinyangxinji/p/oo_homework_sum2.html
總結(jié)
以上是生活随笔為你收集整理的第二单元作业——电梯模拟总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 爱情存在吗-2
- 下一篇: day11 函数的参数列表