1. 程式人生 > >GO語言使用之goroutine(協程)

GO語言使用之goroutine(協程)

一、從需求引入Go協程

要求統計1-9000000000 的數字中,哪些是素數?
1、分析思路:
傳統的方法,就是使用一個迴圈,迴圈的判斷各個數是不是素數。
10000——100000差了5.02S
2、程式碼如下:

package utils

import (
    "time"
    "fmt"
)

// 判斷素數(1~100)


func PrimeNumber() bool {
    falg := true
    var arr  [] int
    for i := 1; i < 200000; i++ {
        falg = numberIsPrime(i)
        if
falg { arr=append(arr,i) } } // fmt.Println("素數arr=",arr) fmt.Println("素數arr長度",len(arr)) return true } func test(n int,m int,arr [] int) { falg := true for i := n; i < m; i++ { falg = numberIsPrime(i) if falg { arr=append
(arr,i) } } } // 判斷一個數是否是素數? // 即只能被1或者自身整除的自然數(不包括1),稱為素數/質數。 func numberIsPrime(num int) bool{ for i := 2; i < num; i++ { if (num % i == 0) { return false; } } return true; } func CodeTime() { start :=time.Now().Unix() fmt.Println("start時間"
,start) PrimeNumber() end :=time.Now().Unix() fmt.Println("end時間",end) res := end -start fmt.Printf("時間差:%d \n",res) }

3、測試時間如下
test.go

package utils

import (
    "testing"
)

func TestPrimeNumber(t *testing.T)  {
    // res :=PrimeNumber()
    res :=PrimeGoroutine()
    if res {
        t.Logf("測試成功")
    }

}

測試結果:
這裡寫圖片描述
4、優化方案:
使用併發或者並行的方式,將統計素數的任務分配給多個goroutine去完成,這時就會使用到goroutine,提高效率

二、goroutine-基本介紹

1、簡述程序和執行緒說明
程序就是程式程式在作業系統中的一次執行過程,是系統進行資源分配和排程的基本單位
執行緒是程序的一個執行例項,是程式執行的最小單元,它是比程序更小的能獨立執行的基本單位。
一個程序可以建立核銷毀多個執行緒,同一個程序中的多個執行緒可以併發執行。
一個程式至少有一個程序,一個程序至少有一個執行緒
2、程式、程序和執行緒的關係示意圖
這裡寫圖片描述

3、併發和並行

多執行緒程式在單核上執行,就是併發
多執行緒程式在多核上執行,就是並行
示意圖:
這裡寫圖片描述

併發:因為是在一個cpu上,比如有10個執行緒,每個執行緒執行10毫秒(進行輪詢操作),從人的角度看,好像這10個執行緒都在執行,但是從微觀上看,在某一個時間點看,其實只有一個執行緒在執行,這就是併發。

並行:因為是在多個cpu上(比如有10個cpu),比如有10個執行緒,每個執行緒執行10毫秒(各自在不同cpu上執行),從人的角度看,這10個執行緒都在執行,但是從微觀上看,在某一個時間點看,也同時有10個執行緒在執行,這就是並行

三、Go協程和Go主執行緒

1、基本介紹
Go主執行緒(有程式設計師直接稱為執行緒/也可以理解成程序): 一個Go執行緒上,可以起多個協程,你可以這樣理解,協程是輕量級的執行緒[編譯器做優化]。

2、Go協程的特點

  • 有獨立的棧空間
  • 共享程式堆空間
  • 排程由使用者控制
  • 協程是輕量級的執行緒

四、快速入門的案列:

1、案例說明
請編寫一個程式,完成如下功能:
在主執行緒(可以理解成程序)中,開啟一個goroutine, 該協程每隔1秒輸出 “hello,world”
在主執行緒中也每隔一秒輸出”hello,golang”, 輸出10次後,退出程式
要求主執行緒和goroutine同時執行.

2、主執行緒和協程執行流程圖:
這裡寫圖片描述

3、程式碼如下:

package main

import (
    "time"
    "strconv"
    "fmt"
)

/*
請編寫一個程式,完成如下功能:
在主執行緒(可以理解成程序)中,開啟一個goroutine, 該協程每隔1秒輸出 "hello,world"
在主執行緒中也每隔一秒輸出"hello,golang", 輸出10次後,退出程式
要求主執行緒和goroutine同時執行.

*/

//編寫一個函式,每隔1秒輸出 "hello,world"
func test() {
    for i := 1; i <= 10; i++ {
        fmt.Println("tesst () hello,world " + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

func main() {

    go test() // 開啟了一個協程

    for i := 1; i <= 10; i++ {
        fmt.Println(" main() hello,golang" + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

4、測試結果如下:
這裡寫圖片描述

5、總結:
1)主執行緒是一個物理執行緒,直接作用在cpu上的。是重量級的,非常耗費cpu資源。
協程從主執行緒開啟的,是輕量級的執行緒,是邏輯態。對資源消耗相對小。
2)Golang的協程機制是重要的特點,可以輕鬆的開啟上萬個協程。其它程式語言的併發機制是一般基於執行緒的,開啟過多的執行緒,資源耗費大,這裡就突顯Golang在併發上的優勢了。

五、goroutine的排程模型

1、MPG模式基本介紹
這裡寫圖片描述
M: 作業系統的主執行緒 (是物理執行緒)
P: 協程執行需要的上下文
G: 協程

2、MPG模式執行的狀態1
這裡寫圖片描述
1)當前程式有三個M, 如果三個M都在一個cpu執行,就是併發,如果在不同的cpu執行就是並行
2)M1,M2,M3正在執行一個G,M1的協程佇列有三個,M2的協程佇列有3個, M3協程佇列有2個
3)從上圖可以看到: Go的協程是輕量級的執行緒,是邏輯態的,Go可以容易的起上萬個協程。
4)其它程式c/java的多執行緒,往往是核心態的,比較重量級,幾千個執行緒可能耗光cpu

3、MPG模式執行的狀態2
這裡寫圖片描述
1)分成兩個部分來看
2)原來的情況是 M0 主執行緒正在執行G0協程,另外有三個協程在佇列等待
3)如果G0協程阻塞,比如讀取檔案或者資料庫等
4)這時就會建立M1主執行緒(也可能是從已有的執行緒池中取出M1),並且將等待的3個協程掛到M1下開始執行, M0的主執行緒下的G0仍然執行檔案io的讀寫。
5)這樣的MPG排程模式,可以既讓G0執行,同時也不會讓佇列的其它協程一直阻塞,仍然可以併發/並行執行。
6)等到G0不阻塞了,M0會被放到空閒的主執行緒繼續執行(從已有的執行緒池中取),同時G0又會被喚醒。

六、設定Golang執行的cpu數

基本介紹:
為了充分了利用多cpu的優勢,在Golang程式中,設定執行的cpu數目。

func CpuDemo()  {
    //獲取當前系統cpu數量
    cpuNum := runtime.NumCPU()
    fmt.Println("cpuNum=", cpuNum)

    //可以自己設定使用多個cpu,設定num-1的cpu執行程式
    runtime.GOMAXPROCS(cpuNum - 1)
    fmt.Println("ok")
}

go1.8後,預設讓程式執行在多個核上,可以不用設定了
go1.8前,還是要設定一下,可以更高效的利益cpu