2020北航OO第二單元總結

2022-06-19 15:09:15 字數 3311 閱讀 7099

任務要求:通過多執行緒的互斥和同步控制,實現電梯功能模擬,並進行擴充套件和迭代。

第一次作業:單電梯,由於請求完成時間有限制,所以需要進行捎帶或者其他優化策略。

第二次作業:多部電梯,增加每部電梯人數限制,到達樓層可擴充套件到負數層。

第三次作業:多部電梯,每部電梯可到達樓層、速度、人數限制不同。需要支援乘客換乘。可以在執行過程中增加電梯。

大致思路為乙個最最基本的生產者消費者模型。一共兩個執行緒,其中請求輸入類作為生產者執行緒,電梯類作為消費者執行緒,排程器作為共享變數管理請求佇列。作為生產者,請求輸入類會將請求加入到排程器的請求佇列中,電梯類則作為消費者根據請求佇列中的請求進行執行,並在相應層將人加入到電梯當中,成為電梯佇列的一元。注意,這裡我並沒有將電梯中人組成的佇列當成請求佇列,因為我並不關心佇列順序,這和我的排程演算法有關。所以說我一共只有乙個請求佇列,維護起來十分方便。

關於排程演算法,我只根據請求佇列裡的請求以及電梯內的人的請求來對電梯執行的方向進行轉換。當電梯和請求佇列中都沒有人時,電梯類的 direction 變數置為0,並 wait() 。當電梯暫停,即為 direction 變數為0時,根據當前請求列表的第乙個請求的所在樓層,改變電梯的方向,即 direction 的值,以使其向請求所在樓層執行。在電梯執行過程中,只有當前執行方向上仍有請求佇列中請求的所在層,或者有當前電梯中的人的目標層,那麼便不改變執行方向,否則使電梯反向執行或停止。每當電梯在執行時經過一層,都會掃瞄電梯內部有無要出去的人,或者當前層需要進電梯的人。只要二者滿足其一便開門,出人進人,關門。這個排程演算法實現簡單,且效能不錯,後來我才知道原來這就是look演算法。

第二次作業的迭代需要新加的**非常少。首先,為滿足可以到達負數層的,在電梯類中加入乙個樓層與連續整數的對映關係。其次,為實現多部電梯,只需開啟多個執行緒即可。最麻煩的一步是設定電梯限制(其實也不麻煩)。在掃瞄請求佇列中是否有請求在電梯的執行方向時,需要提前判斷電梯是否已滿,如果電梯已滿,便忽略請求佇列中的請求。而在每一層進人時,如果電梯已經滿了,就不再進人了。

這樣下來,仍然是乙個簡單的生產者消費者模型,排程器甚至還是只用乙個(我三次作業排程器都是單例模式)。由於多個電梯執行緒自己跑自己的,對於請求的競爭相當公平,相當於請求會平均分配給各個電梯,可以充分利用所有電梯。最後資料的效能分沒有低於99的,還有幾個100,效能出乎意料的好。

第三次作業的迭代需要新加的**依然非常少,這一單元的可擴充套件性做的真的不錯,不像第一單元每次都是整個重寫。我仍然是只有乙個排程器,排程器不會分配請求給電梯,每個電梯還是自己跑自己的。也因為這樣,加入新電梯的任務十分簡單,只需要直接新開啟乙個執行緒。在電梯物件的構造方法中,根據電梯型別對資料(包括可到達樓層,速度,人數限制)進行初始化。在開門前加入判斷,當前樓層是否為該電梯可到達樓層,是的話才開門。另幾處需要更改的地方:掃瞄請求佇列中在電梯執行方向上的請求時,忽略不能到達的樓層,同理在判斷是否要停下時,也要忽略不能到達樓層的請求。除此之外,電梯仍然還在第一次作業中一模一樣的排程演算法在可到達的樓層直接來回掃瞄即可。

對於實現換成,我在 person 類中加入了 destination 變數,初始為0。在請求加入佇列時便判斷是否必須換乘,若必須換乘,則將 destination 賦值為 tofloor (目標層),將 tofloor 賦值為要換乘的層。在人出電梯時,判斷 destination是否為0,若不為0,則將 tofloor 改為 destination , destination 置0,重新加入到請求佇列中。

該方法不僅實現簡便(排程器不用分級,直接讓電梯自己跑,不用處理請求分配),而且強測中效能表現十分優異,效能分基本沒有低於99.8的(除了兩個弱智bug,某種電梯寫錯樓層了...),非常出人意料。

這裡只分析第三次作業,三次作業的相似性非常強,從第一次到第三次都沒有新增類,只是在各個類中新增了方法和資料。

可以看出邏輯結構十分清晰,排程器作為共享變數來對執行緒進行互斥和同步,因此方法眾多,稍顯冗餘,且有些方法重複性高,其實可以通過過載overload進行合併。不同類直接分工較為明確,可擴充套件性強,感覺整體架構還是不錯的

elevator.setdirection() 方法的控制語句最多,設計複雜度iv(g)和圈複雜度v(g)最高,用於根據請求佇列和電梯內佇列判斷是否改變方向,是排程演算法中最最核心的部分,複雜不可避免。方法的非結構化程度基本都不高,圈複雜度v(g)和模組設計複雜度iv(g)除  elevator.setdirection()以外基本表現良好。

可以看出每個類內聚性都不錯,排程器類稍差,但由於其中有很多控制線程安全的方法,也是可以接受的。可以看出排程器類和電梯類完成了大部分工作,說明該兩類的方法應該做進一步拆分。

我承認這單元偷懶了,沒有進行必要的測試,導致一些非常弱的bug產生,讓我十分後悔。因此我也沒有hack別人,整個就摸了。所以這裡就來說說我的反思。

第一次作業被hack出乙個ctle的bug,原因是因為使用了暴力輪詢來使電梯獲取排程器請求佇列內的請求,不停 while (true) 消耗太多cpu資源。後來在請求佇列沒有請求的時候 wait() 一下就好了。反思:多看討論區的討論,在de這個bug之前我都不知道暴力輪詢是啥。

第二次作業是乙個特例的bug,即只要最開始在第一層開門進人,那麼所有沒有競爭到請求的電梯都會在原地(1層)arrive 一下,which是不應該的。其實這就是乙個分支控制的小問題,而且1層開門這種資料太弱,實在不應該沒有測試到,就算自己搞自動化測評,遵循覆蓋原則也應該手動構造出這類資料,實在是不應該,白得優異的效能,真的是教訓。

第三次作業的bug更弱,原因是c類電梯的換乘出了小錯誤,且僅在從3層上電梯,去往4層時會出現這個bug。無奈這一小種情況仍被強測兩個資料逮住了。我想這種資料想要自己測出來唯有測評機大量跑了,這麼看來自動測評還是很有必要的。

這單元主要是多執行緒安全的訓練,但我在寫程式的過程中並沒有太多遇到執行緒安全的問題,甚至寫**的有效性要比上一單元高。我的理解是一定要想清楚你執行緒執行的時序,什麼時候停,什麼時候喚醒,由誰來喚醒,這些問題想好之後,其他的事情迎刃而解。此外在寫下每乙個共享物件時都在腦子裡過一遍這個物件在其他執行緒中可能會進行哪些操作,將其依照時序排列組合一番,確定自己正在寫下的操作在任何一種情況下都不會出問題才能放心寫下,這正是我在進行迭代修改的過程中所做的,根據所要新加的功能整體過一遍**,過完也就改完了。

北航2020OO第二單元部落格作業

本次作業需要實現一部簡單電梯的執行,很自然的,我選擇將本次作業的三個部分,讀入資料 排程資料 電梯執行作為三個執行緒來執行。其中讀入資料的執行緒比較簡單,根據提供的介面使用方法順序讀入即可。排程資料我實現了乙個排程器類,用於管理兩個佇列,乙個是讀入資料的佇列,乙個是給電梯分配任務的佇列。電梯作為乙個...

OO第二單元總結

本單元的作業總體來說比較愉快,畢竟不像上次一樣次次重構。本單元為電梯系列問題,涉及到多執行緒問題。簡單起見,我使用的是生產者 消費者模式。本次作業要求實現單部可稍帶電梯。看完題目後我認為生產者 消費者模式非常適合解決這個問題。本次電梯我採用的是look方法。本方法核心即在於電梯方向的判斷,這在dis...

OO第二單元總結

共享資料類 在總結後面的3.基於度量的程式結構分析部分,本人根據展示的uml類圖更加詳細的講解了具體的協同結構工作原理。通過對實現以上操作的共享資料類中的方法設定synchronized,從而實現執行緒對共享資料的訪問同步。ocplsp ispdip 根據以上類圖,分析本次作業設計思路如下 2 根據...