進程、執行緒和協程的區別
在面試中,經常會有面試官問題「進程、執行緒和協程的區別」這個問題,這也是大學課程作業系統中最基本的知識。我們通常對此都說上幾句,但細節又不是特別深入明了。我整理了一下相關的內容,加上自己的理解,與君共享。
1. 進程
1.1 定義
進程是電腦中程式的一次運行活動,是作業系統進行資源分配和調度的基本單位。每一個進程都擁有自己的地址空間,一般包括程式碼段、數據段、堆和棧。其中,程式碼段用來存放處理器執行的程式碼;數據段存放全局和靜態變數;堆用來存放動態分配的記憶體;棧用來存放局部變數、函數參數和暫存器的值等。
1.2 進程切換
多個進程同時運行時,為了保證所有進程都能獲得執行機會,就需要按照一定的演算法不斷地進行進程切換。所謂進程切換就是從運行中的進程中收回處理器,然後再使待運行進程來佔用處理器。從某個進程收回處理器,實質上就是把進程運行過程中暫存器的中間數據存放到進程的堆棧。讓某個進程來佔用處理器,實質上是把這個進程存放在堆棧中的暫存器數據恢復到處理器的暫存器中去,並把待運行進程的斷點送入處理器的程式計數器。
一個進程存儲在處理器各暫存器中的中間數據叫做進程的上下文,所以進程切換就是被中止進程與待運行進程上下文的切換。進程切換上下文時,需要進出作業系統內核,並進行暫存器數據切換等工作,都需要一定的時間開銷。
1.3 進程函數
-
pid_t fork(void)
功能:創建一個子進程
-
int clone(int (*fn)(void ), voidchild_stack, int flags, void *arg)
功能:複製一個子進程
-
pid_t getpid(void)
功能:獲取自己的進程id
-
pid_t wait(int *status) or pid_t waitpid(pid_t pid, int *status, int options)
功能:阻塞父進程,直到子進程結束或者接收到指定的訊號
2. 執行緒
2.1 定義
執行緒是進程中的一個實體,是處理器調度和分派的基本單位,它是比進程更小的能獨立運行的單元。執行緒基本上不擁有獨立的系統資源,它與同屬一個進程的其他的執行緒共享進程擁有的資源,執行緒獨自擁有少量的程式計數器、數據暫存器和棧等運行中必不可少的私有資源。因此,作業系統調度執行緒比進程付出的開銷小得多,利用執行緒能夠有效地提高系統的並發效率。
總之進程是資源分配的基本單位,執行緒是調度的基本單位。
2.2 共享資源
執行緒共享了所屬進程的資源,包括:
- 記憶體地址空間
- 進程基礎資訊
- 打開的文件
- 訊號處理
- 當前工作目錄
- 用戶和用戶組屬性
2.3 執行緒函數
-
pthread_create(tid,&attr,func,&arg)
功能:創建一個新的執行緒。
-
pthread_join(tid,void **retval)
功能:等待執行緒號為tid的執行緒執行結束後回收執行緒資源,類似於進程的wait()函數,阻塞進程。
-
pthread_exit(void *retval)
功能:結束執行緒,並返回結束碼
-
pthread_cancel(tid)
功能:取消執行緒
-
pthread_self(void)
功能:獲取執行緒id號
3. 協程
3.1 定義
協程本質上是一種用戶態執行緒,它不需要作業系統來進行調度,而是由用戶程式自行管理和調度。它寄存於執行緒中,系統開銷極小,可以顯著的提高性能和並發能力。使用協程的優點是運行效率高、編程簡單、結構清晰;缺點是需要程式語言的支援,如果不支援,則需要在用戶自行實現調度器。目前,原生支援協程的語言不是很多。
協程有自己的上下文,同屬一個進程的協程共享進程擁有的系統資源。協程的切換由自己控制,由切換到其他協程由當前協程來控制。與執行緒和進程相比,協程的最大優勢在於其「輕量級」,可以輕鬆創建上百萬個而不會導致系統資源衰竭。
3.2 go語言的協程
go語言在語言級別支援協程,go的協程叫goroutine。go語言標準庫提供的所有系統調用操作,都會出讓 處理器給其它goroutine,這使得協程切換管理不再依賴於系統的執行緒和進程。
go協程與執行緒的性能比較:
-
記憶體消耗
goroutine:2KB
執行緒:8MB
-
調度切換開銷
goroutine:只修改PC/SP/DX暫存器的值
執行緒:模式切換用戶態和內核態之間的模式切換)、所有暫存器的刷新
通過關鍵字go 就啟動了一個 goroutine。
package main
import ( ·
"fmt"
"time"
)
func echo(s string) {
fmt.Println(s)
}
func main() {
// 啟動兩個協程(goroutine)
go echo("Hello world 1")
go echo("Hello world 2")
// 阻塞3秒,等待協程執行完畢
time.Sleep(3*time.Second)
}