Go程式語言:支援併發、垃圾回收的編譯型系統級程式語言!Go語言從語言層面上就支援了併發,這與其他語言大不一樣,不像以前我們要用Thread庫 來新建執行緒,還要用執行緒安全的佇列庫來共享資料。
一、併發concurrency
1.基本概念
Go能處理高併發的根本原因在於執行go協程只需極少的棧記憶體(大概4~5KB),並且能根據需要動態增長和縮減佔用的資源。
Go的高併發其實是由goroutine實現的,goroutine是由官方實現的超級“執行緒池”。
簡單而言,goroutine就是一段程式碼,一個函式入口,以及在堆上為其分配的一個堆疊。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所排程執行,而是通過系統的執行緒來多路派遣這些函式的執行,使得每個用go
關鍵字執行的函式可以執行成為一個單位協程。當一個協程阻塞的時候,排程器就會自動把其他協程安排到另外的執行緒中去執行,從而實現了程式無等待並行化執行。而且排程的開銷非常小,一顆CPU排程的規模不下於每秒百萬次,這使得在程式中能夠建立大量的goroutine,實現高併發的同時,依舊能保持高效能。
Goroutine 奉行通過通訊來共享記憶體,而不是共享記憶體來通訊。
2.區別併發和並行
這裡涉及到一個問題,很多同學搞不清楚併發與並行的區別,這裡我根據我根據知乎上這個問題某位網友的例子,我覺得很好:
- 你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支援併發也不支援並行
- 你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支援併發
- 你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支援並行
併發:你有處理多個任務的能力,不一定同時(一個CPU輪流)
並行:有同時處理多個任務的能力(多個CPU同時)
併發和並行都可以是很多個執行緒,就看這些執行緒能不能同時被(多個)CPU執行,可以說明是並行,併發是多個執行緒被一個CPU輪流切換著執行。
併發不是並行:Concurrency Is Not Parallelism
併發主要由切換時間片來實現“同時”執行,而並行則是直接利用多核實現多執行緒的執行,但Go可以設定使用核數,以發揮多核計算機的能力。
併發時,由於cpu執行的太快了,不停地來回切換,讓人以為是同時進行。
併發比並行更優秀,充分的利用了CPU。
3.通道Channel
-
Channel
是goroutine
溝通的橋樑,大都是阻塞同步的 - 通過
make
建立,close
關閉 - Channel 是引用型別
- 可以使用
for range
來迭代不斷操作channel - 可以設定單向或雙向通道
- 可以設定快取大小,在未被填滿前不會發生阻塞
4.Select
- 可處理一個或多個channel的傳送與接收
- 同時有多個可用的channel時按隨機排序處理
- 可用空的 select 來阻塞 main 函式
- 可設定超時
二、併發示例
1.未使用channel關鍵字
package main
import (
"fmt"
"time"
)
func main() {
go Go() // 使用go關鍵字就可執行goroutine
time.Sleep(2 * time.Second)
fmt.Println("Hey ...!")
}
func Go() {
fmt.Println("Go ...!")
}
列印:
➜ src go run myfirstgo/concurrency.go
Go ...!
Hey ...!
2.使用channel關鍵字
package main
import (
"fmt"
)
func main() {
// 建立channel
c := make(chan bool)
// 執行到此處會阻塞
go func() {
fmt.Println("Go ...!")
c <- true
}()
// 將true的東西存到channel當中,然後讀出來結束執行,即通知main函式,我這裡執行完成了,可以結束了
<-c
fmt.Println("Hey ...!")
}
列印:
➜ src go run myfirstgo/concurrency.go
Go ...!
Hey ...!
3.range示例
func main() {
// 建立channel
c := make(chan bool)
// 執行到此處會阻塞
go func() {
fmt.Println("Go ...!")
c <- true
close(c)
}()
for v := range c {
fmt.Println(v)
}
fmt.Println("Hey ...!")
}
列印:
➜ src go run myfirstgo/concurrency.go
Go ...!
true
Hey ...!
4.多核
package main
import (
"fmt"
"runtime"
)
// 當使用單執行緒執行時,會按部就班,按照順序1,2,3,4執行下去
// 當使用多個CPU核數時,任務分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(c, i)
}
<-c
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
if index == 9 {
c <- true
}
}
執行多次的執行結果:
➜ src go run myfirstgo/concurrency.go
3 4999999950000001
0 4999999950000001
1 4999999950000001
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
2 4999999950000001
1 4999999950000001
3 4999999950000001
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
2 4999999950000001
9 4999999950000001
當使用多個CPU核數(runtime.GOMAXPROCS)時,任務分配是不定的,所以會出現上邊的結果。
這裡有兩種解決方案:
第一種:
設定一個快取長度為10的channel
package main
import (
"fmt"
"runtime"
)
// 當使用單執行緒執行時,會按部就班,按照順序1,2,3,4執行下去
// 當使用多個CPU核數時,任務分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(c, i)
}
// 設定一個快取長度為10 的channel
for i := 0; i < 10; i++ {
<-c
}
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
c <- true
}
列印:
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
2 4999999950000001
9 4999999950000001
3 4999999950000001
7 4999999950000001
0 4999999950000001
8 4999999950000001
6 4999999950000001
4 4999999950000001
5 4999999950000001
➜ src go run myfirstgo/concurrency.go
0 4999999950000001
9 4999999950000001
5 4999999950000001
1 4999999950000001
7 4999999950000001
2 4999999950000001
8 4999999950000001
3 4999999950000001
6 4999999950000001
4 4999999950000001
第二種:
不是通過channel解決的,而是通過sync
包解決的,它有一個waitGroup
,可以建立一個任務組,可以新增任務,每完成一個任務,就標記完成Done
,main函式的主要作用就是判斷是否所有的任務都完成了,如果都完成了,則退出程式。
package main
import (
"fmt"
"runtime"
"sync"
)
// 當使用單執行緒執行時,會按部就班,按照順序1,2,3,4執行下去
// 當使用多個CPU核數時,任務分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
// sync
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go Go(&wg, i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
wg.Done()
}
列印:
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
5 4999999950000001
7 4999999950000001
9 4999999950000001
6 4999999950000001
8 4999999950000001
0 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
9 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
5 4999999950000001
0 4999999950000001
6 4999999950000001
7 4999999950000001
8 4999999950000001
相關文章:
golang語言併發與並行——goroutine和channel的詳細理解(一)
golang的goroutine排程機制