進程 (Process)、線程 (Thread)、協程 (Coroutine) 的概念講解

從以上的圖,可以一眼看出 Process、Thread、Coroutine 的關係~

最近在研究 Golang 語言的 goroutine,得到一個新的概念,就是協程,以往我學 OS 的時候,只聽過進程 (Process)、線程 (Thread),因此這篇文章就來好好整理究竟三者的概念及區別為何。

另外,其實台灣的翻譯 Process 會翻成行程、Thread 翻成執行緒,大陸翻譯是進行跟線程,不過看了看去感覺大陸的翻譯比較習慣 XD,會翻成執行緒是因為實際上在執行任務是 Process 內的 Thread。

進程 (Process)

定義

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

事實上,當你打開你的 Windows 的工作管理員,就可以看到一堆進程:

可以看到現在我的電腦開了許多應用程式而每一個應用程式就是對應於一個進程,可以看到應用程式後面有括號,你可以想成就是該應用程式我開多少個實例,每一個實例對應到都是一個進程,而進程對於 OS 而言就是一個任務的意思。此外,每一個進程都有各自對應的獨立地址空間。

進程主要由哪些元件組成

  1. code section

    指的就是程式碼區域,因為每一個進程都是因為執行某段程式碼而開啟的。

  2. data section

    指的就是數據區域,代表進程會用到哪些資源

  3. programming counter

    指的是程式計數器,負責記錄下一個要執行程式的地址

  4. cpu registers

    指的是 cpu 的暫存器的內容,專門暫存指令、資料和位址的記憶體空間

  5. stack region

    指的是堆疊區域,負責存放 process 活動過程需要調用的指令及本地變量

但 Process 跟 Program 差別又在哪?

簡單來說,Program 就是一般我們用程式語言在檔案中寫好的 code,而當 Program 被執行了,才會形成 Process!所以 Program 是被動的在硬碟裡面等待執行,Process 是主動的,是一個正在執行的 Program。

進程狀態

一個進程會依據情況而產生以下不同的狀態:

  • new (新產生):該進程正在產生中
  • ready (就緒):該進程正在等待 CPU 分配資源,只要一拿到資源就可以馬上執行
  • running (執行):該進程取得 CPU 資源並且執行中
  • waiting (等待):該進程在等待某個事件的發生,可能是等待 I/O 設備輸入輸出完成或者是接收到一個信號,也可以想成是被 block (阻塞) 住
  • exit (結束):該進程完成工作,將資源釋放掉

進程的優缺點

優點:相對比較穩定安全,因為每一個進程都擁有獨立的系統資源,進程間不容易相互影響,而且因為不會共享 data 的問題,所以不須對進程作互斥存取之機制。

缺點:進程的建立及切換 (context switching) 的開銷都比較大,因為涉及到 OS 資源的切換,彼此進程間要通信也比較複雜及耗時。

並發 (Concurrent) vs 並行 (Parallel)

講完進程,就可以先來看何謂並發與並行,這兩個名詞其實會常常搞錯,可以先看看下圖來解釋:

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

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

線程 (Thread)

定義

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

線程的組成

一個標準的線程組成主要會有:

  • Thread ID
  • Programming Counter
  • CPU registers
  • stack

多線程的涵義

當一個線程裡面有多個線程同時執行,就能執行多個任務,但其實也可以是多個進程,每個進程單一線程達到執行多個任務的效果,但是因為多個進程之間是不會共享資源,所以切換進程會需要很多成本,反之,線程因為會共享,切換的成本較小,提高了 OS 的並發性能。這也是為什麼會有多線程的方式產生。

而 CPU 多核的方式,也可以將一個進程裡面的多個線程分散到不同核心上變成並行的方式執行任務,達到實質上同時做任務!

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

以 Java 為例,開發多線程的程式,就是創建 Thread 的方式來達成。

協程 (Coroutine)

定義

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

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

與線程的比較

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

Goroutine 的誕生

可以看看官方對 goroutine 的描述:

Goroutines are part of making concurrency easy to use. The idea, which has been around for a while, is to multiplex independently executing functions—coroutines—onto a set of threads. When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won’t be blocked. The programmer sees none of this, which is the point. The result, which we call goroutines, can be very cheap: they have little overhead beyond the memory for the stack, which is just a few kilobytes.

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

總結

這次文章簡單了介紹何謂進程、線程、協程,而且協程算是近年來新的概念,之前都只有學過進程、線程而已,我想這也是為什麼 Golang 會特別吸引人的原因,之後下篇文章帶來 goroutine 底層模型的介紹!

最後最後!請聽我一言!

如果你還沒有註冊 Like Coin,你可以在文章最下方看到 Like 的按鈕,點下去後即可申請帳號,透過申請帳號後可以幫我的文章按下 Like,而 Like 最多可以點五次,而你不用付出任何一塊錢,就能給我寫這篇文章的最大的回饋!