您好,登錄后才能下訂單哦!
在談goroutine之前,我們先談談并發和并行。
一般的程序,如果沒有特別要求的話,是順序執行的,這樣的程序也容易編寫維護。但是隨著科技的發展、業務的演進,我們不得不變寫可以并行的程序,因為這樣有很多好處。
比如你在看文章的時候,還可以聽著音樂,這就是系統的并行,同時可以做多件事情,充分地利用計算機的多核,提升軟件運行的性能。
在操作系統中,有兩個重要的概念:一個是進程、一個是線程。當我們運行一個程序的時候,比如你的IDE或者QQ等,操作系統會為這個程序創建一個進程,這個進程包含了運行這個程序所需的各種資源,可以說它是一個容器,是屬于這個程序的工作空間,比如它里面有內存空間、文件句柄、設備和線程等。
那么線程是什么呢?線程是一個執行的空間,比如要下載一個文件,訪問一次網絡等。線程會被操作系統調用,來在不同的處理器上運行編寫的代碼任務,這個處理器不一定是該程序進程所在的處理。操作系統的調度是操作系統負責的,不同的操作系統可能會不一樣,但是對于我們程序編寫者來說,不用關心,因為對我們都是透明的。
一個進程在啟動的時候,會創建一個主線程,這個主線程結束的時候,程序進程也就終止了,所以一個進程至少有一個線程。這也是我們在main函數里,使用goroutine的時候,要讓主線程等待的原因,因為主線程結束了,程序就終止了,那么就有可能會看不到goroutine的輸出。
Go語言中并發指的是讓某個函數獨立于其他函數運行的能力,一個goroutine就是一個獨立的工作單元,Go的runtime(運行時)會在邏輯處理器上調度這些goroutine來運行,一個邏輯處理器綁定一個操作系統線程,所以說goroutine不是線程,它是一個協程,也是這個原因,它是由Go語言運行時本身的算法實現的。
這里我們總結下幾個概念:
概念 | 說明 |
---|---|
進程 | 一個程序對應一個獨立程序空間 |
線程 | 一個執行空間,一個進程可以有多個線程 |
邏輯處理器 | 執行創建的goroutine,綁定一個線程 |
調度器 | Go運行時中的,分配goroutine給不同的邏輯處理器 |
全局運行隊列 | 所有剛創建的goroutine都會放到這里 |
本地運行隊列 | 邏輯處理器的goroutine隊列 |
當我們創建一個goroutine后,會先存放在全局運行隊列
中,等待Go運行時的調度器
進行調度。把他們分配給其中的一個邏輯處理器
,并放到這個邏輯處理器對應的本地運行隊列
中,最終等著被邏輯處理器
執行即可。
這一套管理、調度、執行goroutine的方式稱之為Go的并發。并發可以同時做很多事情,比如有個goroutine執行了一半,就被暫停執行其他goroutine去了,這是Go控制管理的。所以并發的概念和并行不一樣,并行指的是在不同的物理處理器上同時執行不同的代碼片段,并行可以同時做很多事情;并發是同時管理很多事情,因為操作系統和硬件的總資源比較少,所以并發的效果要比并行好的多,使用較少的資源做更多的事情,也是Go語言提倡的。
Go的并發原理我們剛剛講了,那么Go的并行是怎樣的呢?其實答案非常簡單,多創建一個邏輯處理器
就好了,這樣調度器就可以同時分配全局運行隊列
中的goroutine到不同的邏輯處理器
上并行執行。
func main() { var wg sync.WaitGroup wg.Add(2) go func(){ defer wg.Done() for i:=1;i<100;i++ { fmt.Println("A:",i) } }() go func(){ defer wg.Done() for i:=1;i<100;i++ { fmt.Println("B:",i) } }() wg.Wait() }
這是一個簡單的并發程序。創建一個goroutine是通過go
關鍵字的,其后跟一個函數或者方法即可。
這里的sync.WaitGroup
其實是一個計數的信號量,使用它的目的是要main
函數等待兩個goroutine執行完成后再結束,不然這兩個goroutine
還在運行的時候,程序就結束了,看不到想要的結果。
sync.WaitGroup
的使用也非常簡單,先是使用Add
方法設置計算器為 2 ,每一個goroutine的函數執行完之后,就調用Done
方法減 1 。Wait
方法的意思是如果計數器大于 0 ,就會阻塞,所以main
函數會一直等待兩個goroutine完成后,再結束。
我們運行這個程序,發現A和B前綴會交叉出現,并且每次運行的結果可能不一樣,這就是Go調度器調度的結果。
默認情況下,Go默認是給每個可用的物理處理器都分配一個邏輯處理器,因為我的電腦是 4 核的,所以上面的例子默認創建了 4 個邏輯處理器,所以這個例子中同時也有并行的調度,如果我們強制只使用一個邏輯處理器,我們再看看結果。
func main() { runtime.GOMAXPROCS(1) var wg sync.WaitGroup wg.Add(2) go func(){ defer wg.Done() for i:=1;i<100;i++ { fmt.Println("A:",i) } }() go func(){ defer wg.Done() for i:=1;i<100;i++ { fmt.Println("B:",i) } }() wg.Wait() }
設置邏輯處理器個數也非常簡單,在程序開頭使用runtime.GOMAXPROCS(1)
即可,這里設置的數量是 1 。我們這時候再運行,會發現先打印A,再打印B。
這里我們不要誤認為是順序執行,這里之所以順序輸出的原因,是因為我們的goroutine執行時間太短暫了,還沒來得及切換到第 2 個goroutine,第 1 個goroutine就完成了。這里我們可以把每個goroutine的執行時間拉長一些,就可以看到并發的效果了,這里不再示例了,大家自己試試。
對于邏輯處理器的個數,不是越多越好,要根據電腦的實際物理核數。如果不是多核的,設置再多的邏輯處理器個數也沒用。如果需要設置的話,一般我們采用如下代碼設置。
runtime.GOMAXPROCS(runtime.NumCPU())
所以對于并發來說,就是Go語言本身自己實現的調度;對于并行來說,是和運行的電腦的物理處理器的核數有關的,多核就可以并行并發,單核只能并發了。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。