golang 併發 並行

2022-04-11 09:01:49 字數 3698 閱讀 4212

go 語言的執行緒是併發機制,不是並行機制。

那麼,什麼是併發,什麼是並行?

併發是不同的**塊交替執行,也就是交替可以做不同的事情。

並行是不同的**塊同時執行,也就是同時可以做不同的事情。

舉個生活化場景的例子:

你正在家看書,忽然**來了,然後你接**,通話完成後繼續看書,這就是併發,看書和接**交替做。

如果**來了,你一邊看書一遍接**,這就是並行,看書和接**一起做。

package main

import (

"fmt"

"runtime"

"sync"

)func main()

wg.add(20)

for i := 0; i < 10; i++ ()

} for i := 0; i < 10; i++ (i)

} wg.wait()

}

輸出:

go routine 2 i: 9

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 1 i: 10

go routine 2 i: 0

go routine 2 i: 1

go routine 2 i: 2

go routine 2 i: 3

go routine 2 i: 4

go routine 2 i: 5

go routine 2 i: 6

go routine 2 i: 7

go routine 2 i: 8

為什麼會是這樣的結果?

雖然我們在for迴圈中使用了go 建立了乙個goroutine,我們想當然會認為,每次迴圈變數時,golang一定會執行這個goroutine,然後輸出當時的變數。 這時,我們就陷入了思維定勢。 預設併發等於並行。

誠然,通過go建立的goroutine是會併發的執行其中的函式**。 但一定會按照我們所設想的那樣每次迴圈時執行嗎? 答案是否定的!

rob pike專門提到了golang中併發指的是**結構中的某些函式邏輯上可以同時執行,但物理上未必會同時執行。而並行則指的就是在物理層面也就是使用了不同cpu在執行不同或者相同的任務。

golang的goroutine排程模型決定了,每個goroutine是執行在虛擬cpu中的(也就是我們通過runtime.gomaxprocs(1)所設定的虛擬cpu個數)。 虛擬cpu個數未必會和實際cpu個數相吻合。每個goroutine都會被乙個特定的p(虛擬cpu)選定維護,而m(物理計算資源)每次回挑選乙個有效p,然後執行p中的goroutine。

每個p會將自己所維護的goroutine放到乙個g佇列中,其中就包括了goroutine堆疊資訊,是否可執行資訊等等。預設情況下,p的數量與實際物理cpu的數量相等。因此當我們通過迴圈來建立goroutine時,每個goroutine會被分配到不同的p佇列中。而m的數量又不是唯一的,當m隨機挑選p時,也就等同隨機挑選了goroutine。

在本題中,我們設定了p=1。所以所有的goroutine會被繫結到同乙個p中。 如果我們修改runtime.gomaxprocs的值,就會看到另外的順序。 如果我們輸出goroutine id,就可以看到隨機挑選的效果:

package main

import (

"fmt"

"runtime"

"strconv"

"strings"

"sync"

)func main()

wg.add(20)

for i := 0; i < 10; i++

fmt.printf("go routine 1 : i -- %d id-- %d\n ", i, id)

wg.done()

}()} for i := 0; i < 10; i++

fmt.printf("go routine 2 : i -- %d id-- %d \n", i, id)

wg.done()

}(i)

} wg.wait()

}

輸出:

go routine 1 : i -- 10  id-- 20

go routine 1 : i -- 10 id-- 19

go routine 1 : i -- 10 id-- 22

go routine 1 : i -- 10 id-- 25

go routine 1 : i -- 10 id-- 28

go routine 2 : i -- 8 id-- 37

go routine 1 : i -- 10 id-- 23

go routine 1 : i -- 10 id-- 21

go routine 2 : i -- 0 id-- 29

go routine 2 : i -- 9 id-- 38

go routine 2 : i -- 1 id-- 30

go routine 2 : i -- 5 id-- 34

go routine 2 : i -- 2 id-- 31

go routine 2 : i -- 6 id-- 35

go routine 2 : i -- 3 id-- 32

go routine 1 : i -- 10 id-- 24

go routine 2 : i -- 7 id-- 36

go routine 1 : i -- 10 id-- 27

go routine 2 : i -- 4 id-- 33

go routine 1 : i -- 10 id-- 26

我們再回到這道題中,雖然在迴圈中通過go定義了乙個goroutine。但我們說到了,併發不等於並行。因此雖然定義了,但此刻不見得就會去執行。需要等待m選擇p之後,才能去執行goroutine。 關於golang中goroutine是如何進行排程的(gpm模型),可以參考scalable go scheduler design doc或者learnconcurrency

這時應該就可以理解為什麼會先輸出goroutine2然後再輸出goroutine1了吧。

下面我們來解釋為什麼goroutine1中輸出的都是10.

在golang的for迴圈中,golang每次都使用相同的變數例項(也就是題中所使用的i)。 而golang之間是共享環境變數的。

再結合題中的for迴圈,每次使用的都是同乙個變數位址,也就是說i每次都在變化,到迴圈結束之時,i就變成了10. 而goroutine中儲存的也只有i的記憶體位址而已,所以當goroutine1執行時,毫不猶豫的就把i的內容讀了出來,多少呢? 10!

但為什麼goroutine2不是10呢?

反過來看goroutine2,就容易理解了。因為在每次迴圈中都重新生成了乙個新變數,然後每個goroutine儲存的是各自新變數的位址。 這些變數相互之間互不干擾,不會被任何人所篡改。因此在輸出時,會從0 - 9依次輸出。

golang 序列與並行(併發)對比

golang對比其它語言最大的優勢就是乙個go關鍵字就能實現併發,工作中經常遇到併發的場景,具體實現併發的方式有多種,今天就來做個小實驗來對比說明序列和並行執行任務中併發的優勢,好了直接上 package main import fmt math rand sync time 定義任務數量 var ...

golang 多核並行

go語言具有支援高併發的特性,可以很方便地實現多執行緒運算,充分利用多個cpu的效能。眾所周知伺服器的處理器大都是單核頻率較低而核心數較多,對於支援高併發的程式語言,可以充分利用伺服器的多核優勢,從而降低單核壓力,減少效能浪費。go語言實現多核多執行緒併發執行是非常方便的,下面舉個例子 packag...

Golang非CSP併發模型外的其他並行方法總結

golang最為讓人熟知的併發模型當屬csp併發模型,也就是由goroutine和channel構成的gmp併發模型,具體內容不在贅述了,可以翻回之前的文章檢視。在這裡,要講講golang的其他併發方式。golang不僅可以使用csp併發模式,還可以使用傳統的共享資料的併發模式。這是傳統語言比較常用...