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