這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
2015-04-14 wcdj
在GoLang之Concurrency再討論一文討論過並發的幾個問題,分別如下:
1,goroutine是否並發的問題
2,goroutine非並發安全性問題
3,並發情況下的原子操作問題
本文在上述討論的基礎上,進一步對goroutine的調度機制,以及並發的實現原理進行討論。
0 goroutine 輕量的調度單位
程式員使用goroutine可以快速地開發一個並發(可能非並行)執行的程式。goroutine是協程(coroutine,輕量級進程,Light Weight Process)的一種實現,Linux POSIX C也提供了coroutine系統調用。協程(輕量級進程),就是在使用者空間實現調度,比線程更輕量的執行單元。協程的調度不是搶佔式的,這就意味著同一線程內的協程只有主動放棄CPU,調度器才會將另外一個等待的協程調度進來然後執行。goroutine在Go語言的運行時環境(runtime)中實現,在使用者空間內被調度於一個或多個線程之間(而非限定在一個線程中)。當一個goroutine調用了某個可能阻塞的標準庫函數之後,這個goroutine則會被運行時環境暫停,另一個準備好的goroutine則被調度進來然後開始執行。
原則上,Go語言運行時環境中的調度器與作業系統的調度器並沒有太大區別,都是在某個事件發生時,將最小調度單位暫停或啟動,但goroutine的調度在使用者空間內執行,因此調度速度更快,並且可以支援大量並發的goroutine同時運行。
Go語言是一個完全編譯型的語言(靜態語言),它的代碼被直接編譯成本地機器碼執行,因此不存在中間的解譯器或者虛擬機器。要實現goroutine運行時的功能,Go語言是利用運行時環境來完成的。Go的程式在連結時,都會被加入一套運行時環境。這套環境類似於一個靜態庫,包含來Go語言中規定的一些運行時特性,例如,記憶體回收,goroutine調度等。在Go語言標準庫中,在每個可能產生阻塞的函數中,都會最終通知運行時環境中的調度器,讓調度器將當前的goroutine暫停,這樣的設計可以極大地簡化並發(可能非並行)程式的開發。
1 goroutine與丟失的select/poll/epoll等非阻塞調用
在Go語言中,是否提供了類似C語言中select/poll/epoll函數?如何讓一個網路連接使用非阻塞I/O?怎麼對網路連接進行非同步讀寫?
這幾個問題回答是:Go的網路程式不使用非同步作業,一切操作都是同步的,每個goroutine處理一個串連。
在Go代碼層面,開發人員看到的是使用goroutine來進行阻塞式讀寫,而在Go的內部實現中,則是利用非同步作業,通過對goroutine的調度來完成對事件的處理。作為最小的調度單位,goroutine之間是並發(可能非並行)執行的。