OO 第二單元總結:調度祭天,法力無邊


心得體會

早春的風沙滌盪着京城上下,楊柳的毛絮洗刷了校園內外,無邊的任務積攢在自己身上。

盡管世人可能不是都清楚這一點,但是時間和精力對於我來說是相對有限的。繁復的任務奪走了我沉思的時間,滿日的奔波枯竭了我靈動的精力,剩給自己的便只剩浮躁的碎片化知識面擴充,和不免帶來負罪感的忙里偷閑式的娛樂。

有的時候如果一些東西讓你煩心,不如試着直接把它拋到九霄雲外。

第五次作業

就算是一萬丈那么高的樓閣也要從平地開始建造,這是亘古不變的道理。就連西方的學者,在開發最精密頂尖的技術時,也需要從所謂的基線開始。對於電梯系統來說,這樣精髓關鍵的步驟,難道不正是框架設計嗎?我深深地認為這樣的認識是合理的。自打少年時光,盡管那時的我還未進入高等教育學府,我在完成任務的時候,便沒有不先想清楚各處實現方法便魯莽開工,直接上手開始寫代碼的。如此這般,每每到了被指派任務的時候,必然先將其放在腦海中晃盪一會。將其與腦漿混合均勻,下到碎片化思考的鍋中,兩面炸至金黃后出鍋。這時候,一般來說也快到預先設定的期限了。然后這時的我便像考試結束鈴響還在奮筆疾書的考生一般,用必死的精神試圖在鈴結束前寫完答案那樣,充分運用自己的聰明才智和編程基礎,試圖把這樣一份成果呈遞到甲方面前。

有朋友聽說了這樣那樣的我經常在做的事情,驚奇道:“不愧是 ptw,輕易地就做到了我們做不到的事情!” 我非常感謝他們對我游走在截止期限邊緣的勇氣的認可。我聽說,久經風霜的戰馬能夠找到歸鄉的路徑,手刃千牛的庖丁可以定位牛身的脈絡。然而熟悉的事物容易被人們忽略,最為危險的境地反而是最佳的容身之處。經常在河畔、在隰地邊行走的人,難免偶爾會把鞋子沾濕。學業的隱患,也這樣藏在了日常的生活節奏之中。在面向對象的第五次作業中,使着一手老革命的工作方法的我,就這樣遇到了新問題。一天乃至半天即可解決面向對象作業的印象,自預習任務產生,由第一單元的三次作業加深,在本單元甫一開始時尚未顯現出其危險性,但使我直到周日才發現:自主設計電梯調度器並非一朝一夕之功,而需瞻前顧后之勞。

就像統領千軍的將軍所持有的虎符一般重要,在電梯系統設計的指導中,沒有不出現調度這個概念的。從高加索山到西西里島,從課上的實驗代碼到評論區的伙計們,盡管一千個設計者就有一千種電梯系統,但仿佛沒有了調度器,便無法設計電梯系統似的。對此深刻的認識使我不得不開始着重思考調度器的設計,並在一開始便將其作為我的電梯設計的重要組成部分來思考。

然而精疲力盡的我實在是整不動活了,所以經過日日夜夜的思考,最后我不得不采取長話長說的方式,以更貧瘠的文字來表達我的意思。

周三到周六我一直在糾結調度器和電梯之間的交互形式。是給電梯發上下開關的指令流,還是直接指定目標樓層讓它運行到指定點,還是給電梯指定運載請求?如果電梯接受指令流,那么電梯的邏輯就相對簡單,但調度器就需要動態獲取電梯狀態。電梯狀態是動態變化的,如果讓所有電梯在調度器執行時阻塞,那么調度器就大大地增加了系統運行時間,大大地削弱了多線程的並行性。如果電梯不阻塞,那么調度器獲取到的電梯狀態就是不准確的,有可能跟實際情況不一致,增加了保持指令正確性的難度。如果直接指定目標樓層,首先在狀態上會面臨同樣的問題,而且電梯也需要自己管上下人,那么究竟載上誰也難以由調度器控制,因為這要求調度器追蹤電梯到達每個層(就是電梯到達某層時均需要喚醒調度器)。如果給電梯指定運載請求,我們的電梯、調度器、請求池之間的耦合又相當強,而且調度器需要掌握所有人和所有電梯,在這之上做最優化需要相對復雜的邏輯。盡管我覺得大概率上是因為我自己比較傻想不到怎么去優雅地實現這些邏輯,但這讓我覺得十分地剪不斷理還亂,沒有多線程和面向對象的味,反而充滿了玄學調參優化的氣息。

終於,在糾結於幾種交互粒度之間的來來往往之間,寶貴的工期流逝殆盡。盡管數位助教送上了暖心的關懷,但我還是沒能選擇出一個最佳的實現方式來順利地把任務完成。這也成了我面向對象這門課中第一次的無效作業,使我對我對多線程編程思想的理解程度的信心又動搖了幾分。

第六次作業

  • 159 行
  • 6.52 KB
  • 強測 99.3262

調度器設計

本次作業中沒有調度器。

在經歷了第五次作業無效的慘痛后,我痛定思痛,但關於調度器設計還是沒有什么比較好的主意。

正當我山窮水盡疑無路的時候,柳暗花明之處,一些奇特的想法開始涌現。

如果我們把調度器(Scheduler)扔掉?

我們直接讓所有電梯自由競爭所有用戶需求。人一進來,電梯們一起蜂擁而上,具體誰搶到,就交給 JVM 決定吧。

電梯運行邏輯類似常見電梯(聽說這叫 LOOK?),能載則載,自覺干活,充分發揮集體經濟的力量。

拿掉調度器后,項目進度推進神速,代碼邏輯酣暢淋漓,真可謂是一日千里。

如果說這套東西里還有什么是起到調度作用的“調度器”的話,那可能只剩 JVM 了吧。

同步塊介紹

// Elevator.java
synchronized (pool) {
    if (pool.isEmpty() && persons.isEmpty()) {
        if (pool.isEnd()) { return; }
        try { pool.wait(); }
        catch (InterruptedException e) { e.printStackTrace(); }
    }
}

// InputThread.java
synchronized (pool) {
    pool.addRequest((PersonRequest) req);
    pool.notifyAll();
}

// RequestPool.java
public synchronized void addRequest(PersonRequest req) { pool.put(req.getPersonId(), req); }

public synchronized Stream<Integer> extreme(int dir) {
    return pool.values().stream().map(u -> dir * u.getFromFloor());
}

public synchronized PersonRequest[] getReq(int floor, int dir) {
    return pool.values().stream().filter(
        u -> {
            int f = u.getFromFloor();
            int t = u.getToFloor();
            return f == floor && dir * (t - f) >= 0;
        }
    ).toArray(PersonRequest[]::new);
}

public synchronized ArrayList<PersonRequest> getReq(int floor, int dir, long cap) {
    PersonRequest[] qwq = getReq(floor, dir);
    ArrayList<PersonRequest> ret =
            new ArrayList<>(Arrays.asList(qwq).subList(0, (int) Math.min(cap, qwq.length)));
    ret.forEach(u -> pool.remove(u.getPersonId()));
    return ret;
}

使用的鎖只有請求池的鎖。鎖使得對請求池的關鍵讀寫操作具有原子性。

第七次作業

  • 171 行
  • 7.47 KB
  • 強測 99.9072

同步塊介紹

// Elevator.java
synchronized (pool) {
    if (pool.extreme(0, reachPred).count() == 0 && persons.isEmpty()) {
        if (pool.isEnd()) { return; }
        try { pool.wait(); }
        catch (InterruptedException e) { e.printStackTrace(); }
    }
}

// InputThread.java
synchronized (pool) {
    pool.addRequest((PersonRequest) req);
    pool.notifyAll();
}

synchronized (pool) {
    pool.terminate();
    pool.notifyAll();
}

// RequestPool.java
public synchronized void addRequest(PersonRequest req) { pool.put(req.getPersonId(), req); }

public synchronized Stream<Integer> extreme(int dir, Predicate<Integer> pred) {
    return pool.values().stream().filter(u -> pred.test(u.getToFloor()))
            .map(PersonRequest::getFromFloor).filter(pred).map(u -> u * dir);
}

public synchronized PersonRequest[] getReq(int floor, int dir, Predicate<Integer> pred) {
    return pool.values().stream().filter(
        u -> {
            int f = u.getFromFloor();
            int t = u.getToFloor();
            return f == floor && dir * (t - f) >= 0 && pred.test(t) && pred.test(f);
        }
    ).toArray(PersonRequest[]::new);
}

public synchronized ArrayList<PersonRequest> getReq(
        int floor, int dir, long cap, Predicate<Integer> pred) {
    PersonRequest[] qwq = getReq(floor, dir, pred);
    ArrayList<PersonRequest> ret =
            new ArrayList<>(Arrays.asList(qwq).subList(0, (int) Math.min(cap, qwq.length)));
    ret.forEach(u -> pool.remove(u.getPersonId()));
    return ret;
}

使用的鎖只有請求池的鎖。鎖使得對請求池的關鍵讀寫操作具有原子性。

調度器設計

本次作業中沒有調度器。

電梯直接從請求池主動獲取請求並完成運載工作。

架構設計分析

其中請求池並不作為線程存在,只是為了方便展示其與其他部分的交互情況而特地表出。

Bug 分析

印象中我在公測和互測中沒有出過 bug。

只記得有一次 WA 是因為忘了加上電梯 ID 輸出。

Hack 策略

我沒有 hack 別人。

我只是把他們的代碼下下來隨便跑了跑。

其中有一份代碼運行時間是我 3 倍多,可惜叉不掉 = =

重構經歷總結

除了開始糾結架構,基本沒有重構過。

之后為了稍微節省一點代碼行數,把一些模塊化的函數給內聯化了,這是為了整活而做的一些比較犧牲優雅性的小規模重構,不值一提。

心得體會

寫在開頭了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM