前言: 這是一篇面向對象作業總結,作業內容是模擬電梯調度,一共有三個階段,具體要求不詳述,第一階段只要求先來先服務電梯,第二次支持捎帶,第三次則需要多部電梯協調,通過換乘來完成請求。本次作業在優化方面效果不佳。設計比較統一,設計原則檢查放在最後。 第5次作業 類圖如下: 說明: 具體的來說,M是主入 ...
前言:
這是一篇面向對象作業總結,作業內容是模擬電梯調度,一共有三個階段,具體要求不詳述,第一階段只要求先來先服務電梯,第二次支持捎帶,第三次則需要多部電梯協調,通過換乘來完成請求。本次作業在優化方面效果不佳。設計比較統一,設計原則檢查放在最後。
第5次作業
類圖如下:
說明:
具體的來說,M是主入口,也是主線程,目的是獲取請求。Loader是容器,也只存在一個單例ld,其中req表是其管理的實質,當要synchronized多個實例時,loader優先上鎖以防死鎖。DianTi則是從屬線程,目的是模擬電梯的運行。
電梯會向loader獲得第一個請求,如果沒有得到就迴圈,直到M放入請求時喚醒它,如果獲得到請求,則先到那個層,然後取得所有當前層乘客,接下來先向上走再向下走,每一層詢問ld有無請求並決定開門與否,其中管理in隊列以獲得請求,直到in隊列空,開始下一輪。輪詢而沒有wait()。
這裡涉及到一個監視器,就是結束標誌,它封裝於loader內部的mainEndFlg,當M結束時設置mainEndFlg,電梯如果沒得到請求又發現M已經結束,則將自己結束了。
類圖中的另外兩個是用於debug的,模擬測評機進行輸入按時投射。
代碼度量結果如下:
從度量結果中可以看出,我沒有用子類,這次環複雜度沒標紅,而是嵌套塊深度深了點,主要體現在dianTi的run方法,其抽象還不夠。
Bug:
在強測時出現了bug,原因是輪詢之前沒有將進行goup()和godown(),乘客被鎖在電梯里。
第6次作業
類圖如下:
說明:
本次作業與之前一次作業改動不大,具體來說就是添加了一層過濾輸入,使得樓層內部表示是-2,-1,0,1~16,輸出樓層的修正是在dianTi內部函數完成,因為輸出必然只有dianTi才能控制。
第二點就是wait和notify。本次作業新增了nextTo,作為一個監聽器,當沒有請求時電梯通過nextTo.wait()進入睡眠,當有請求來到ld時,nextTo.notifyAll()喚醒所有進程。這些都封裝於loader內部。
優化不理想的原因在於每次電梯詢問請求時ld都將其指向人數最多的一層,這在隨機情況下統計特性(可能)不如走到最近一層。
度量分析:
這一次我將dianti的run方法進行了一點抽取,所以發現沒紅。7個靜態方法都是一些輔助debug和計算max、min、abs的程式,總方法比之前多了,變為52個。
環複雜度最高的時goDown和goUp,這兩個是功能性方法,攤平有助於修改。不過它們確實是細粒度的並行。
Bug:
第一個bug就是因為沒有考慮0層不可達,通過添加一個類解決之。第二就是CPU輪詢時間太長,通過wait解決之。第三個bug沒體現出來,留在下一次作業中。
第7次作業
類圖如下:
說明:
這次作業需要考慮多部電梯協調、不可達樓層以及乘客換乘的問題。這裡加了一層reqDistri,也是一個線程,其實也可以省略,因為計算量並不算太大,它存在的目的是為了對M的輸入進行處理,由乘客請求得出一條可達路徑,插入ld隊列。
一開始使用弗洛伊德演算法,發現最多兩次換乘,後期加入preSche層,原理是獲得每個電梯當前狀態的臨時展板dianTiData,然後由起點和終點之間依次嘗試插入每一層,用一些啟髮式的方法決定路徑。
加入pair輔助類,拓展了myPerson的內容,使之涵蓋了一條請求路徑,但是沒有直接將其分給哪個電梯,ld是被動等待請求的,電梯則是隨機獲得請求的,通過判斷請求路徑的第一個pair是否是自己可達的來決定讓不讓乘客上電梯。當有電梯去往某一層時,下一個電梯則詢問ld則自動跳過那一層,以期望電梯分散搭載乘客。
停止條件方面,將mainEndFlg放在reqDistr里,reqDistr在M結束後就結束了,而電梯則是需要所有電梯都結束且不可能再有請求時,才能結束。所以ld還要訪問所有in隊列看看其是否為空,每個電梯通過ld的isEmpty方法知道別的電梯的in是否空,以及ld是否還會再有請求。
後期為了優化,在每個電梯層數變化時,強行更新ld.req里所有請求的路徑,並通知所有的電梯,以期望電梯能夠更加智能。這樣會對性能有一定提升。
度量結果如下:
可以看出還是沒有紅色的,不過總代碼行數增加了許多,主要是嘗試了兩種啟髮式的方法,一種就是最短距離的電梯先到,不考慮上下行走狀態,另外一種是考慮上下行並且上下行到底就會等待。其實是第一種的性質比較好,而第二種的假設比較多,如果真正實現了完全預運行,則性能應該會更好,望下次改進。要完成這些,需要一個狀態展示板,牽涉到了很多新增屬性。
Bug:
如果測試到了需要wait的時候再次進行goUp(),goDown(),然後切換cpu發現有新的請求入隊,接著電梯進入wait模式,然後發現沒有更多的請求去喚醒它,這樣它們就死等下去。解決方法就是再次判斷是否需要wait,並且wait時間不超過5秒。這樣對cpu還是可以接受的。
設計原則檢查
Single Responsibility Principle:mainEndFlg和nextTo兩個監視器可以抽離出來,其餘基本是專註於管理自己隊列的程式。
Open Close Principle:大架構基本不變,有些地方需要加點代碼重構,而更多的功能性要求則是通過添加方法和添加類來實現的。
Liscov Substitution Principle:根本沒有子類,不用替換了。
Interface Segregation Principle:沒有介面
Dependency Inversion Principle:抽象的就abs,getlock和release這些static的方法,其他都是源於細節的構架。所以抽象依賴無從談起。
總結
覺得多線程最重要的就是生產者和消費者模型,worker thread是其變種。所以有多線程的地方首先看其buffer是什麼,有幾層buffer,在一些對性能要求比較高的圖形學架構上就使用這種多層流水的模式,也是生產者消費者模型的變種。
輪詢可以交給一個特定的進程(比如jvm)去做,雖然還是要輪詢檢查時間,但是畢竟需要輪詢的線程少,則輪詢效率更高,線程則用wait的機制等待時間就好。
缺點在於,沒有很好的預模擬程式,並且電梯沒有一個預請求隊列,每個電梯都是隨機搶到哪一層的,所以性能得分不高。