鐵人賽 程式運行概念的演進 - Jian-Min-Huang/tech-note GitHub Wiki

前言

以前一個 single core 大抵上單純不少,變不出什麼大花樣來。跑 multi thread 雖然可以榨出一點效能,但還須思考 pending 行為之於 context switch 的效益是否划算。

不過硬體世界跟隨 moore's law 多年,科技早已日新月異,硬體運行能力上升的同時,也給軟體設計增添不少難度。

multi core 的架構下,理論上如果你的程式設計成 multi thread,是真的會被並行處理的。這點源於現代作業系統設計任務排程的基本工作粒度是以 thread 為單位。

但即使如此,也並不是 thread 開飽開滿就能無限地增長效能,這會跟 core numbers、executing time、waiting time 扯上關係,也就是即使你的程式可以高度被平行化,thread 的增長之於效能的增長也是到一個高峰就不會在上去,甚至可能會掉下來。另外 multi thread 帶來的其他難題還有像是鎖、任務排程和生命週期等等都是會深深的影響你 BUG 的產生及系統的設計。

那我們今天要來講什麼呢?要來講軟體運行一路這樣走來從 Process -> Thread -> Routine 有啥改變,新的 Routine 能變出什麼新花樣呢?還有介紹一些抽象概念。

Process

這很好理解,大家都從這邊入手,在單核單緒的狀況下就是一次做一件事,一個東西卡住就全部停住。而在多執行緒之外其實是有人在寫多核編程做平行處理,以這張圖片來看。 每一個任務都是一個 process,然後你可以看到 chrome 開了很多個,因為他就是用一個分頁用一個 process 來實作的。

Thread

thread 就是現代作業系統做任務調度的基本單位,理論上多執行緒跑在多核心上就可以並行處理。可以避免程式因為外在的 pending 導致效能的降低,善用寶貴的 CPU 資源,他的架構有點像這樣。 除了有同一個 process 共用的資源,也有屬於個別 thread 的資源

Thread Pool

承前言的部份,既然執行緒不能無限制的增長,那就要好好管理在一個數量下進行調用、回收及複用,同時這個模式我覺得一定程度啟發了一些新的運行抽象概念,像是 Routine、Reactive 和 Actor 底層都有一個 thread pool 在調度任務。

Routine or Fiber

前面我們說過,現代作業系統調度的單位問題。所以我們來試想一個情境,如果你現在連 thread 都不用自己寫,你調度的單位又更一步細化了會怎麼樣。就像 thread pool 所要處理的問題,為了方便編程,現代又提出了一個新的概念,Routine or Fiber。你 coding 調度的單位不再是寫一個 thread,而是定義出 Routine or Fiber 要做的事。而實務上要執行的時候,是由背景的 thread pool 在調度,但其實這個 thread pool 卻又是由後面作業系統(core)來調度,所以粒度有細分下去的感覺,自然這種框架的工,難度就會發生在你怎麼去協調任務了。不過今天我們並不會細講,如果大家有興趣可以參考 Go Routine、Kotlin Coroutine 或 Java Quasar。

Reactive

細項的設計、概念,請大家直接參照官網或是網路上其他教學。這個要講幾乎又可以講個一篇,還是又有人想聽我講!?哈哈。總之我直接貼上一段原始碼讓大家聞香。

public class FluxPublishOn {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        Flux.range(1, 20)
                //使用Schedulers.parallel()线程池执行之后的操作
                .publishOn(Schedulers.parallel())
                .doOnComplete(() -> countDownLatch.countDown())
                .subscribe(i -> {
                    System.out.println("Current Thread is "
                            + Thread.currentThread().getName() + ", value " + i);
                });
        //如果使用了Scheduler,则subscribe是异步的,主线程必须阻塞才行
        System.out.println(Thread.currentThread().getName() + "-Main thread blocking");
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "-Flow complete,Main thread run and finished");
    }
}

請注意到 Schedulers.parallel() 跟其他那些一系列資料的操作,他們實作了什麼這我們不管,但我希望講解的部份是,這些每一個步驟有點像是我們的上面說的 Routine 並且是由這個 Scheduler 在調度。從這個角度出發希望可以讓你體會那種粒度更細的感覺。

Actor

這也是一個博大精深的東西,最早出於 1973 Carl Hewitt 教授的論文,後來 Erlang 把他實作出來,並且發揚光大。再後來 Akka 用 Scala 也實作了 Actor Model。詳細的簡單教學可以看J葛,那這跟上面的有什麼關係呢,該不會又有 Scheduler 了吧?哇塞你怎麼那麼聰明,請看。如果你的收發 message 的這個行為我們看成你定義的 Routine,再配上 Scheduler 來調度,484又跟剛剛其實一樣了,只是因為他用這個 message 收發的概念,同時他也有 event-driven 的感覺,真的很華麗對吧,所以我本人算是 Akka 粉,只可惜他跟K8s設計理念有點小衝突,這個有機會再講,今天就先這樣囉。


About Me

Jian-Min Huang

wide range skill set backend engineer

Research, Architecture, Coding, DB, Ops, Infra.

mainly write Java but also ❤️ Scala, Kotlin and Go

http://github.jianminhuang.cc

http://linkedin.jianminhuang.cc

http://note.jianminhuang.cc

[email protected]