goroutine-基本介紹程序員
進程和線程介紹golang
程序、進程和線程的關係示意圖編程
併發和並行安全
1)多線程程序在單核上運行,就是併發
2)多線程程序在多核上運行,就是並行
3)示意圖:數據結構
小結:多線程
Go 協程和Go 主線程併發
Go 主線程(有程序員直接稱爲線程/也能夠理解成進程): 一個 Go 線程上,能夠起多個協程,你能夠這樣理解,協程是輕量級的線程[編譯器作優化]。編程語言
Go 協程的特色
1)有獨立的棧空間
2)共享程序堆空間
3)調度由用戶控制
4)協程是輕量級的線程函數
示意圖測試
goroutine-快速入門
案例說明
請編寫一個程序,完成以下功能:
1)在主線程(能夠理解成進程)中,開啓一個 goroutine, 該協程每隔 1 秒輸出 "hello,world"
2)在主線程中也每隔一秒輸出"hello,golang", 輸出 10 次後,退出程序
3)要求主線程和 goroutine 同時執行.
4)畫出主線程和協程執行流程圖
輸出的效果說明, main 這個主線程和 test 協程同時執行.
主線程和協程執行流程圖
快速入門小結
1)主線程是一個物理線程,直接做用在 cpu 上的。是重量級的,很是耗費 cpu 資源。
2)協程從主線程開啓的,是輕量級的線程,是邏輯態。對資源消耗相對小。
3)Golang 的協程機制是重要的特色,能夠輕鬆的開啓上萬個協程。其它編程語言的併發機制是通常基於線程的,開啓過多的線程,資源耗費大,這裏就突顯 Golang 在併發上的優點了
goroutine 的調度模型
MPG 模式基本介紹
MPG 模式運行的狀態 1
MPG 模式運行的狀態 2
設置 Golang 運行的cpu 數
介紹:爲了充分了利用多 cpu 的優點,在 Golang 程序中,設置運行的 cpu 數目
channel(管道)-看個需求
需求:如今要計算 1-200 的各個數的階乘,而且把各個數的階乘放入到 map 中。最後顯示出來。要求使用 goroutine 完成
分析思路:
1)使用 goroutine 來完成,效率高,可是會出現併發/並行安全問題.
2)這裏就提出了不一樣 goroutine 如何通訊的問題
代碼實現
1)使用 goroutine 來完成(看看使用 gorotine 併發完成會出現什麼問題? 而後咱們會去解決)
2)在運行某個程序時,如何知道是否存在資源競爭問題。 方法很簡單,在編譯該程序時,增長一個參數 -race 便可 [示意圖]
package main import ( "fmt" "time" ) // 需求:如今要計算 1-200 的各個數的階乘,而且把各個數的階乘放入到 map 中。 // 最後顯示出來。要求使用 goroutine 完成 // 思 路 // 1. 編寫一個函數,來計算各個數的階乘,並放入到 map 中. // 2. 咱們啓動的協程多個,統計的將結果放入到 map 中 // 3. map 應該作出一個全局的. var ( myMap = make(map[int]int, 10) ) // test 函數就是計算 n!, 讓將這個結果放入到 myMap func test(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } //這裏咱們將 res 放入到 myMap myMap[n] = res //concurrent map writes? } func main() { // 咱們這裏開啓多個協程完成這個任務[200 個] for i := 1; i <= 200; i++ { go test(i) } //休眠 10 秒鐘【第二個問題 】 time.Sleep(time.Second * 10) //這裏咱們輸出結果,變量這個結果 for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } }
示意圖
爲何須要channel
package main import ( "fmt" ) func main() { //演示一下管道的使用 //1. 建立一個能夠存放 3 個 int 類型的管道 var intChan chan int intChan = make(chan int, 3) //2. 看看 intChan 是什麼 fmt.Printf("intChan 的值=%v intChan 自己的地址=%p\n", intChan, &intChan) //3. 向管道寫入數據 intChan<- 10 num := 211 intChan<- num intChan<- 50 // intChan<- 98//注意點, 當咱們給管寫入數據時,不能超過其容量 //4. 看看管道的長度和 cap(容量) fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3 //5. 從管道中讀取數據 var num2 int num2 = <-intChan fmt.Println("num2=", num2) fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3 //6. 在沒有使用協程的狀況下,若是咱們的管道數據已經所有取出,再取就會報告 deadlock num3 := <-intChan num4 := <-intChan num5 := <-intChan fmt.Println("num3=", num3, "num4=", num4, "num5=", num5) }
應用實例
思路分析
代碼實現
package main import ( "fmt" _ "time" ) //write Data func writeData(intChan chan int) { for i := 1; i <= 50; i++ { //放入數據 intChan<- i fmt.Println("writeData ", i) //time.Sleep(time.Second) } close(intChan) //關閉 } //read data func readData(intChan chan int, exitChan chan bool) { for { v, ok := <-intChan if !ok { break } //time.Sleep(time.Second) fmt.Printf("readData 讀到數據=%v\n", v) } //readData 讀取完數據後,即任務完成 exitChan<- true close(exitChan) } func main() { //建立兩個管道 intChan := make(chan int, 50) exitChan := make(chan bool, 1) go writeData(intChan) go readData(intChan, exitChan) //time.Sleep(time.Second * 10) for { _, ok := <-exitChan if !ok { break } } }
應用實例 3
代碼實現
package main import ( "fmt" "time" ) //向 intChan 放入 1-8000 個數 func putNum(intChan chan int) { for i := 1; i <= 8000; i++ { intChan<- i } //關閉 intChan close(intChan) } // 從 intChan 取出數據,並判斷是否爲素數,若是是,就 // //放入到 primeChan func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) { //使用 for 循環 var flag bool for { time.Sleep(time.Millisecond * 10) num, ok := <-intChan if !ok { //intChan 取不到.. break } flag = true //假設是素數 //判斷 num 是否是素數 for i := 2; i < num; i++ { if num % i == 0 {//說明該 num 不是素數 flag = false break } } if flag { //將這個數就放入到 primeChan primeChan<- num } } fmt.Println("有一個 primeNum 協程由於取不到數據,退出") //這裏咱們還不能關閉 primeChan //向 exitChan 寫入 true exitChan<- true } func main() { intChan := make(chan int , 1000) primeChan := make(chan int, 2000)//放入結果 //標識退出的管道 exitChan := make(chan bool, 4) // 4 個 //開啓一個協程,向 intChan 放入 1-8000 個數 go putNum(intChan) //開啓 4 個協程,從 intChan 取出數據,並判斷是否爲素數,若是是,就 //放入到 primeChan for i := 0; i < 4; i++ { go primeNum(intChan, primeChan, exitChan) } //這裏咱們主線程,進行處理 //直接 go func(){ for i := 0; i < 4; i++ { <-exitChan } //當咱們從 exitChan 取出了 4 個結果,就能夠放心的關閉 prprimeChan close(primeChan) }() //遍歷咱們的 primeChan ,把結果取出 for { res, ok := <-primeChan if !ok{ break } //將結果輸出 fmt.Printf("素數=%d\n", res) } fmt.Println("main 線程退出") }
package main import ( "fmt" "time" ) func main() { //使用 select 能夠解決從管道取數據的阻塞問題 //1.定義一個管道 10 個數據 int intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan<- i } //2.定義一個管道 5 個數據 string stringChan := make(chan string, 5) for i := 0; i < 5; i++ { stringChan <- "hello" + fmt.Sprintf("%d", i) } //傳統的方法在遍歷管道時,若是不關閉會阻塞而致使 deadlock //問題,在實際開發中,可能咱們很差肯定什麼關閉該管道. //可使用 select 方式能夠解決 //label: for { select { //注意: 這裏,若是 intChan 一直沒有關閉,不會一直阻塞而 deadlock //,會自動到下一個 case 匹配 case v := <-intChan : fmt.Printf("從 intChan 讀取的數據%d\n", v) time.Sleep(time.Second) case v := <-stringChan : fmt.Printf("從 stringChan 讀取的數據%s\n", v) time.Sleep(time.Second) default : fmt.Printf("都取不到了,不玩了, 程序員能夠加入邏輯\n") time.Sleep(time.Second) return //break label } } }
package main import ( "fmt" "time" ) //函數 func sayHello() { for i := 0; i < 10; i++ { time.Sleep(time.Second) fmt.Println("hello,world") } } //函數 func test() { //這裏咱們可使用 defer + recover defer func() { //捕獲 test 拋出的 panic if err := recover(); err != nil { fmt.Println("test() 發生錯誤", err) } }() //定義了一個 map var myMap map[int]string myMap[0] = "golang" //error } func main() { go sayHello() go test() for i := 0; i < 10; i++ { fmt.Println("main() ok=", i) time.Sleep(time.Second) } }