GO并發程式設計問題
并行和并發
并行(parallel):(并行就是兩個隊列同時使用2個廁所)
指在同一時刻(CPU時間量級 ),有`多條指令`在`多個處理器`上同時執行。需要借助多核CPU來實作
并發:(就是兩個隊列交替使用一間廁所)
宏觀:使用者體驗上,程式在并行執行。
微觀:多個計劃任務,順序執行。在飛快的切換。輪換使用 cpu 時間輪片。 (類似于串行)
程序并發
程式和程序?
程式:編譯成功得到的二進制檔案。 占用 磁盤空間。 死的 1 1
程序:運作起來程式。 占用系統資源。(記憶體) 活的 N 1
可以了解成:
程式 → 劇本(紙) 程序 → 戲(舞台、演員、燈光、道具...)
程序狀态
程序基本的狀态有5種。分别為初始态、就緒态、運作态、挂起(阻塞)态、終止(停止)态。其中初始态為程序準備階段,常與就緒态結合來看。
同步:協同步調。規劃先後順序。
線程同步機制:
互斥鎖(互斥量):建議鎖。 拿到鎖以後,才能通路資料,沒有拿到鎖的線程,阻塞等待。等到拿鎖的線程釋放鎖。
讀寫鎖:一把鎖(讀屬性、寫屬性)。 寫獨占,讀共享。 寫鎖優先級高。
Go語言中的并發程式主要使用兩種手段來實作。
goroutine
和
channel
。
goroutine 又叫做go程 建立于程序中,直接使用 go 關鍵,放置于 函數調用前面,産生一個 go程。 并發
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延時1s
}
}
func main() {
//建立一個 goroutine,啟動另外一個任務
go newTask()
i := 0
//main goroutine 循環列印
for {
i++
fmt.Printf("main goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延時1s
}
}
Goroutine的特性:【重點】
主go程結束,子go程随之退出
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延時1s
}
}
func main() {
//建立一個 goroutine,啟動另外一個任務
go newTask()
fmt.Println("main goroutine exit")
}
runtime包
-
runtime.Gosched():
出讓目前go程所占用的 cpu時間片。當再次獲得cpu時,從出讓位置繼續回複執行。
—— 時間片輪轉排程算法。
package main
import (
"fmt"
"runtime"
)
func main() {
//建立一個goroutine
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
for i := 0; i < 2; i++ {
runtime.Gosched() //import "runtime" 包
/*
屏蔽runtime.Gosched()運作結果如下:
hello
hello
沒有runtime.Gosched()運作結果如下:
world
world
hello
hello
*/
fmt.Println("hello")
}
}
以上程式的執行過程如下:
主協程進入main()函數,進行代碼的執行。當執行到go func()匿名函數時,建立一個新的協程,開始執行匿名函數中的代碼,主協程繼續向下執行,執行到runtime.Gosched( )時會暫停向下執行,直到其它協程執行完後,再回到該位置,主協程繼續向下執行。
-
runtime.Goexit():
return: 傳回目前函數調用到調用者那裡去。 return之前的 defer 注冊生效。
Goexit(): 結束調用該函數的目前go程。Goexit():之前注冊的 defer都生效。
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 終止目前 goroutine, import "runtime"
fmt.Println("B") // 不會執行
}()
fmt.Println("A") // 不會執行
}() //不要忘記()
//死循環,目的不讓主goroutine結束
for {
}
}
-
GOMAXPROCS
調用 runtime.GOMAXPROCS() 用來設定可以并行計算的CPU核數的最大值,并傳回之前的值。
package main
import (
"fmt"
)
func main() {
//n := runtime.GOMAXPROCS(1) // 第一次 測試
//列印結果:111111111111111111110000000000000000000011111...
n := runtime.GOMAXPROCS(2) // 第二次 測試
//列印結果:010101010101010101011001100101011010010100110...
fmt.Printf("n = %d\n", n)
for {
go fmt.Print(0)
fmt.Print(1)
![在這裡插入圖檔描述](https://img-blog.csdnimg.cn/20210221145313977.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BwcHBwcHVzaGNhcg==,size_16,color_FFFFFF,t_70)
}
}
在第一次執行runtime.GOMAXPROCS(1) 時,最多同時隻能有一個goroutine被執行。是以會列印很多1。過了一段時間後,GO排程器會将其置為休眠,并喚醒另一個goroutine,這時候就開始列印很多0了,在列印的時候,goroutine是被排程到作業系統線程上的。
在第二次執行runtime.GOMAXPROCS(2) 時, 我們使用了兩個CPU,是以兩個goroutine可以一起被執行,以同樣的頻率交替列印0和1。
調用 runtime.GOMAXPROCS() 用來設定可以并行計算的CPU核數的最大值,并傳回之前(上一個)的值