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也要写入时,由于资源冲突从而导致程序报错。
②:程序执行示意图

二:全局变量的互斥锁解决gorouting资源竞争方法
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资源竞争问题
①:为什么需要channel
第二大类的全局锁虽然能够解决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
读取完毕

浙公网安备 33010602011771号