一·: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 读取完毕