Go之channel

一·:channel的必要性安全

①:使用gorouting來完成1-200的各個數的階乘,而且把階乘後的數放入map中數據結構

使用-race查看是否存在資源競爭問題併發

package main

import (
	"fmt"
)

var m = make(map[int]int,10)

func jiecheng(i int){
	res := 1
	for x := 1; x <= i; x++ {
		res *= x
	}
	m[i] = res
}

func main() {
	for i := 1; i <= 20; i++{
		go jiecheng(i)
	}
      //防止主線程提早執行完畢,從而致使協程過早退出,故等待10s
      time.Sleep(time.Second*10) for index, value := range m { fmt.Printf("m[%v]=%v\n", index, value) } } 結果 ================== m[19]=121645100408832000 m[2]=2 m[3]=6 m[5]=120 m[7]=5040 m[8]=40320 m[10]=3628800 m[14]=87178291200 m[15]=1307674368000 m[1]=1 m[6]=720 m[9]=362880 m[11]=39916800 m[16]=20922789888000 m[17]=355687428096000 m[18]=6402373705728000 m[13]=6227020800 m[20]=2432902008176640000 Found 2 data race(s) exit status 66

 由上代碼可知,gorouting效率雖高,但在該應用場景下容易出現併發或並行的安全問題,因爲存在資源競爭問題,當其中一個gorouting正在寫入文件,另外一個gorouting也要寫入時,因爲資源衝突從而致使程序報錯。spa

②:程序執行示意圖線程

二:全局變量的互斥鎖解決gorouting資源競爭方法3d

package main

import (
	"fmt"
	"time"
	"sync"
)

var m = make(map[int]int,10)
//聲明全局互斥鎖
var lock sync.Mutex
func jiecheng(i int){
	res := 1
	for x := 1; x <= i; x++ {
		res *= x
	}
	//加入互斥鎖
	//當其中一個gorouting協程正在寫入時,其餘協程進入等待寫入序列
	lock.Lock()
	m[i] = res
	lock.Unlock()
}

func main() {
	for i := 1; i <= 20; i++{
		go jiecheng(i)
	}
	//防止主線程提早執行完畢,從而致使協程過早退出
	time.Sleep(time.Second*10)
     //雖然10秒後全部協程已經運行完畢,且已經解鎖,但主線程並不知道,任然認爲m的數據被鎖,故若是不加互斥鎖會報資源競爭報錯
	lock.Lock()
	for index, value := range m {
		fmt.Printf("m[%v]=%v\n", index, value)
	}
	lock.Lock()
}

結果

[ `go run -race channel.go` ⌛ ]
	m[17]=355687428096000
	m[18]=6402373705728000
	m[19]=121645100408832000
	m[3]=6
	m[8]=40320
	m[10]=3628800
	m[12]=479001600
	m[14]=87178291200
	m[5]=120
	m[6]=720
	m[7]=5040
	m[9]=362880
	m[20]=2432902008176640000
	m[4]=24
	m[11]=39916800
	m[13]=6227020800
	m[15]=1307674368000
	m[1]=1
	m[2]=2
	m[16]=20922789888000

三:使用channel解決gorouting資源競爭問題協程

 ①:爲何須要channelblog

第二大類的全局鎖雖然可以解決gorouting的協調調用問題,但主線程等待gorouting協程所有完成的時間沒法肯定,時間長了會影響程序的效率,時間短則會致使gorouting協程沒有運行完畢,且互斥鎖並不利於多個協程對全局變量的讀寫,在這樣的條件下channel可以完美解決這些問題。隊列

②:channel的基本特色資源

channel本質就是一個數據結構-隊列;

channel遵循先進先出(FIFO)原則;

多gorouting運行時,不須要加鎖,線程安全;

channel有類型,一個string的channel只能放string;

四:channel的定義

①:var 變量名 chan

②:channel是引用類型,必須初始化(make)才能寫入數據

五:channel使用舉例

package main

import (
    "fmt"
)

func main() {
    var int_chan chan int
    //一旦定義容量大小即不可更改
    int_chan = make(chan int, 3)

    //int_chan的本質
    fmt.Printf("int_chan的類型是:%T,int_chan=%v\n", int_chan, int_chan)

    //向int_chan管道寫入數據
    //寫入數據不可超過容量,不然報錯
    int_chan <- 1
    int_chan <- 2
    int_chan <- 3

    //從管道讀取數據
    //讀取數據也不可多讀不然報錯
    num1 := <- int_chan
    num2 := <- int_chan
    num3 := <- int_chan

    //遵循先進先出
    fmt.Printf("num1=%v,num2=%v,num3=%v\n", num1, num2, num3)

    //數據被讀取後,又可重新寫入對應數據
    //被讀出多少,就可放進多少數據
    int_chan <- 4
    int_chan <- 5
    int_chan <- 6
    fmt.Printf("num4=%v", <- int_chan)
} 結果 [ `go run channel.go` | done ]
    int_chan的類型是:chan int,int_chan=0xc00006c080
    num1=1,num2=2,num3=3
    num4=4

 備註:channel的其餘類型一樣道理,如map,struct等等

六:channel的關閉與遍歷

①:channel關閉

package main

import (
	"fmt"
)

func main() {
	var int_chan chan int
	//一旦定義容量大小即不可更改
	int_chan = make(chan int, 3)
	
	int_chan <- 1
	int_chan <- 2
	//channel關閉後將不能寫入,但仍然能夠讀取
	close(int_chan)
	fmt.Println(<- int_chan)
	int_chan <- 3
}
結果
[ `go run channel.go` | done ]
	1
	panic: send on closed channel

 ②:channel的遍歷

在遍歷時若是沒有關閉channel,將會報錯;若是關閉channel後再遍歷將會正常運行(像第五大類的正常讀取,超過部分返回對應類型的默認值)

package main

import (
	"fmt"
)

func main() {
	var int_chan chan int
	//一旦定義容量大小即不可更改
	int_chan = make(chan int, 3)

	int_chan <- 1
	int_chan <- 2
	int_chan <- 3
	close(int_chan)
	for value := range int_chan {
		fmt.Println(value)
	}
}
結果
[ `go run channel.go` | done ]
	1
	2
	3

 七:gorouting與channel的綜合案例(對第一大類案例的修改)

package main

import (
	"fmt"
)

func writeChan(i int, int_chan chan int){
	var res int
	for x := 1; x <= i; x++ {
		res = 1
		for y := 1; y <= x; y++{
			res *= y
		}
		int_chan <- res
		fmt.Printf("寫入數據%v\n", res)
	}
	close(int_chan)
}


func readChan(int_chan chan int, exit_chan chan bool) {
	for value := range int_chan {
		fmt.Printf("讀到數據%v\n", value)
	}
	exit_chan <- true
	close(exit_chan)
}
func main() {
	int_chan := make(chan int, 20)
	exit_chan := make(chan bool, 1)

	go writeChan(20, int_chan)
	go readChan(int_chan, exit_chan)
	for {
		_, ok := <- exit_chan
		if ok {
			break
		}
	}
}
結果 [ `go run channel.go` | done ] 寫入數據1 寫入數據2 寫入數據6 寫入數據24 寫入數據120 寫入數據720 寫入數據5040 寫入數據40320 讀到數據1 讀到數據2 讀到數據6 寫入數據362880 寫入數據3628800 寫入數據39916800 寫入數據479001600 寫入數據6227020800 寫入數據87178291200 寫入數據1307674368000 讀到數據24 讀到數據120 讀到數據720 讀到數據5040 寫入數據20922789888000 寫入數據355687428096000 寫入數據6402373705728000 寫入數據121645100408832000 寫入數據2432902008176640000 讀到數據40320 讀到數據362880 讀到數據3628800 讀到數據39916800 讀到數據479001600 讀到數據6227020800 讀到數據87178291200 讀到數據1307674368000 讀到數據20922789888000 讀到數據355687428096000 讀到數據6402373705728000 讀到數據121645100408832000 讀到數據2432902008176640000

 八:channel使用注意事項

①:能夠聲明channel只讀 var 變量名 <-chan 數據類型

②:能夠聲明channel只讀寫var 變量名 chan<- 數據類型

③:select解決channel阻塞問題

package main

import (
	"fmt"
)

func main() {
	int_chan := make(chan int, 3)

	int_chan <- 1
	int_chan <- 2
	int_chan <- 3
	//傳統遍歷channel須要關閉管道,不然將會報阻塞錯誤
	//但在實際開發中,有時並很差判斷在聲明時候關閉管道
	//select能夠結局上面的問題
	for{
		select {
			case v := <- int_chan :
				fmt.Println(v)
			default :
				fmt.Println("讀取完畢")
				return
		}
	}
}
結果
[ `go run channel.go` | done ]
	1
	2
	3
	讀取完畢
相關文章
相關標籤/搜索