天天看點

GO 淺談goroutine(總結于尚矽谷go)

GO并發程式設計問題

并行和并發

并行(parallel):(并行就是兩個隊列同時使用2個廁所)

指在同一時刻(CPU時間量級 ),有`多條指令`在`多個處理器`上同時執行。需要借助多核CPU來實作
           
GO 淺談goroutine(總結于尚矽谷go)

并發:(就是兩個隊列交替使用一間廁所)

宏觀:使用者體驗上,程式在并行執行。  
微觀:多個計劃任務,順序執行。在飛快的切換。輪換使用 cpu 時間輪片。    (類似于串行)
           

程序并發

程式和程序?
程式:編譯成功得到的二進制檔案。	占用 磁盤空間。	死的	  1	   1
程序:運作起來程式。 占用系統資源。(記憶體)		      活的    N	 1
可以了解成:
程式 → 劇本(紙)		程序 → 戲(舞台、演員、燈光、道具...)
           

程序狀态

程序基本的狀态有5種。分别為初始态、就緒态、運作态、挂起(阻塞)态、終止(停止)态。其中初始态為程序準備階段,常與就緒态結合來看。

GO 淺談goroutine(總結于尚矽谷go)
同步:協同步調。規劃先後順序。
線程同步機制:
    互斥鎖(互斥量):建議鎖。 拿到鎖以後,才能通路資料,沒有拿到鎖的線程,阻塞等待。等到拿鎖的線程釋放鎖。
    讀寫鎖:一把鎖(讀屬性、寫屬性)。 寫獨占,讀共享。 寫鎖優先級高。
           

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核數的最大值,并傳回之前(上一個)的值

go