Skip to main content

Goroutine

https://xiang753017.gitbook.io/zixiang-blog/golang/golang-goroutine-concurrency-duo-zhi-hang-xu-qian-tan https://blog.kennycoder.io/2020/05/16/%E9%80%B2%E7%A8%8B-Process-%E3%80%81%E7%B7%9A%E7%A8%8B-Thread-%E3%80%81%E5%8D%94%E7%A8%8B-Coroutine-%E7%9A%84%E6%A6%82%E5%BF%B5%E8%AC%9B%E8%A7%A3/ https://medium.com/@clu1022/kernel-space-v-s-user-space-1c0fdacddca5 https://xiang753017.gitbook.io/zixiang-blog/golang/golang-goroutine-concurrency-duo-zhi-hang-xu-qian-tan

進程(Process)、線程(Thread)、協程(Coroutine)

進程(Process)

進程指的是執行中程式的一個實例,這個實例是 OS 分配資源的基本單位。它會拿到哪些資源呢?有 CPU Time、Memory、I/O Devices 等等,意思就是說這個進程執行的時候會需要用到多久的 CPU 時間、花費多少記憶體、甚至可能會需要一些 I/O 設備的資源。

優缺點:

優點:相對比較穩定安全,因為每一個進程都擁有獨立的系統資源,進程間不容易相互影響,而且因為不會共享 data 的問題,所以不須對進程作互斥存取之機制。 缺點:進程的建立及切換 (context switching) 的開銷都比較大,因為涉及到 OS 資源的切換,彼此進程間要通信也比較複雜及耗時。

Cache Flow

線程(Thread)

線程又叫做是 light weight process,也就是輕量化的 Process,事實上,Thread 可以想成存在在 Process 裡面,一個進程中至少會有一個線程,而我們前面說進程會去執行任務,其實就是進程裡面的線程去做的,所以沒有進程就沒有線程。而當一個進程裡面有多線程,就代表在一個程式中透過開啟多個線程的方式來完成不同的任務。而線程是 OS 分配 CPU 時間 之對象單位。此外,在一個進程裡面的多個線程會共享進程的系統資源,也就是前面所提進程組成的元件等等。

而最大的缺點:多線程在共享資源會有 race condition 也就是互斥存取的問題產生,這也是開發者在開發多線程的程式需要特別注意的一個點。

協程 (Coroutine)

協程是一種用戶態的輕量級線程,可以想成一個線程裡面可以有多個協程,而協程的調度完全由用戶控制,協程也會有自己的 registers、context、stack 等等,並且由協程的調度器來控制說目前由哪個協程執行,哪個協程要被 block 住。

而相對於線程及進程的調度,是由 CPU 內核去進行調度,因此 OS 那邊其實會有所謂許多的調度算法,並且可以進行搶占式調度,可以主動搶奪執行的控制權。反之,協程是不行的,只能進行非搶佔式的調度。可以理解成,如果 coroutine 被 block 住,則會在用戶態直接切換另外一個 coroutine 給此 thread 繼續執行,這樣其他 coroutine 就不會被 block 住,讓資源能夠有效的被利用,藉此實現 Concurrent 的概念。

相對於線程:

  • 協程只需花幾 KB 就可以被創立,線程則需要幾 MB 的記憶體才能創立
  • 切換開銷方面,協程遠遠低於線程,切換的速度也因此大幅提升

併發(Concurrent)和並行(Parallel)

可以知道 Concurrent 其實就是好幾個任務互相在搶相同的 CPU,搶到了就是優先執行該任務,所以在一個時間點上只有一個任務在執行,而 Parallel 則是每個 CPU 各自負責其任務,而且是同時進行的,無所謂切換的問題。

因此電腦如果是單核的話,只能做到 Concurrent,若是多核心的才可以達到 Parallel,可以很明顯感受到為何現在的電腦速度比以前很快的很大主因。

介紹

Golang 語言的 goroutine 其實就是協程,特別的是在語言層面直接原生支持創立協程,並在 runtime、系统調用等多方面對 goroutine 調度進行封裝及處理。相較於 Java 的建立 Thread,OS 是會直接建立一個 Thread 與其對應,而當兩個 Thread,要互相切換需要透過 kernel space 來進行,會有較大的 context switch 的資源耗費,而 goroutine 是在 user space 上直接實現切換,切換成本小。

goroutine 是Go語言實現的用戶態(User space)線程,主要用來解決操作系統線程太“重”的問題,所謂的太重,主要表現在以下兩個方面:

  • 創建和切換太重
  • 內存使用太重

goroutine 是用户態線程,其創建和切換都在用户空間中完成而無需進入操作系統內核,所以其開銷遠遠小於系統線程的創建和切換;goroutine 啟動時默認棧大小只有2k,在多數情況下已經夠用,即使不夠用,goroutine 的棧也會自動擴大,同時,如果棧過大它還能自動收縮,這樣既沒有棧溢出的風險,也不會造成棧內存空間的大量浪費。

一下簡單說明 GO 協程設計: 進程中存在多線程,劃分為用戶態線程和內核態線程,用戶態線程處理程式碼相關邏輯和變量,內核態線程操作系統資源,用戶態線程靠協程調度器切換,不涉及內核切換,降低 CPU 切換成本,並且創建協程內存佔用(KB)。

Cache Flow

Cache Flow

補充說明:用戶空間(User Space)和內核空間(Kernel Space)

Kernel space 可以執行任意命令,調用系統的一切資源;User space 只能執行簡單的運算,不能直接調用系統資源,必須通過系統接口(又稱 system call),才能向內核發出指令。為了確保安全性, 避免一個使用者的程式去修改其他使用者的程式甚至是作業系統核心, 或是壟斷(monopolize)計算機資源等, 大部分的機器(或是CPU), 至少會提供兩種執行特權(privilege): Kernel mode與User mode。

Cache Flow