Golang Channel
目录
- Golang Channel
- 定义
- 声明
- 易错点
- channel的关闭
- channel遍历
- 注意点
- 顺序执行/普通线程
- 并发执行/协程运行
- 例子
- 注意事项
定义
channel
本质是队列- 线程安全, 多
goroutine
访问时, 不需要加锁,就是说channel
本身线程安全 channel
有类型,一个string
的channel
只能存放string
声明
var identifier chan type
- channel是引用类型
channel
必须初始化才能写入数据,即make
后才能使用identifier <- factor
,将一个数据写入到channel
num := <- identifier
, 可以取出但是不赋值<- identifier
channel
中可以使用interface{}
val,ok :=<-flag
, 通道关闭后,读取返回具体值和是否读到数据
As Follow
func main() {
var intChan chan int
intChan = make(chan int, 3)
//intChan =0xc000102080,&intChan = 0xc000006028
fmt.Printf("intChan =%v,&intChan = %v \n",intChan,&intChan)
//向管道写入数据
intChan <- 2
num := 911
intChan <- num
//不同于slice, channel内容固定
a := <- intChan
fmt.Printf("cap = %v, len = %v \n", cap(intChan),len(intChan))
//取出队列头, 如果channel空就会死锁
fmt.Println(a)
fmt.Printf("cap = %v, len = %v \n", cap(intChan),len(intChan))
}
易错点
func main() {
channel := make(chan interface{},5)
channel <- "1"
channel <- 2
channel <- Cat{"张三"}
<-channel
<-channel
cat := <-channel
//编译期不能判别cat的类型, 虽然结果为main.Cat,{张三}
fmt.Printf("%T,%v\n",cat,cat)
//这里必需使用类型断言, 因为编译期认为cat是interface{}
fmt.Printf("%T\n",cat.(Cat).Name)
}
channel的关闭
使用内置函数close
可以关闭channel
,当channel
关闭后,就不能在向channel
写数据了,但是任然可以从该channel
读数据
func main() {
channel := make(chan interface{},5)
channel <- "1"
channel <- 2
close(channel)
channel <- Cat{"张三"}//panic: send on closed channel
<-channel
<-channel
}
channel遍历
选用for-range
, 使用len()
会造成数据不一致, 再遍历时如果没有关闭管道,就会报dead lock
func main() {
channel := make(chan interface{}, 20)
for i := 0; i < 20; i++ {
channel <- i
}
close(channel)
//channel只返回一个, for-range自动取出channel中的值
for val := range channel {
fmt.Println(val)
}
}
注意点
顺序执行/普通线程
-
如果写入的数量大于
cap
, 就会dead lock -
如果读取的数量大于
cap
,且没有关闭管道,就会dead lock -
如果读取的数量大于
cap
,且关闭管道,读取完管道中的数据后,默认读出管道类型的缺省值,和管道中是否有值test := make(chan int, 3) for i := 0; i < 3; i++ { test <- i } close(test) for i := 0; i < 10; i++ { //如果管道中的数据取完后,ok置为false val,ok :=<-test fmt.Println(a,b) //time.Sleep(time.Second) }
并发执行/协程运行
在协程运行中,会发生阻塞
-
如果只有写操作同时写入的数量大于
cap
,管道会导致当前协程阻塞,而不会dead lock,直到有读取的操作var( channel = make(chan int,2) ) func write(){ fmt.Println("写之前") channel <- 1 channel <- 1 channel <- 1 //当大于cap时,channel会导致当前协程阻塞 fmt.Println("写之后") } func main() { go write() fmt.Println("主线程") for{ } }
-
如果只有读操作,但是管道内没有可以读取的值,就会导致当前协程阻塞
func read() { fmt.Println("读取之前") //如果管道中没有值可以读, channel就会导致当前协阻塞 x,ok := <- channel fmt.Println(x,ok)l fmt.Println("读取之后") } func main() { go read() fmt.Println("主线程") for { } }
-
读写并存, 写操作通过协程,同样会导致写协程阻塞
var ( channel = make(chan int, 2) ) func write(){ fmt.Println("写之前") channel <- 1 channel <- 1 channel <- 1 channel <- 1 //运行到这里的时候,channel会导致当前协程阻塞 fmt.Println("写之后") } func read() { fmt.Println("读取之前") //<-channel//如果管道中没有值可以读, channel就会导致当前协阻塞 x,ok := <- channel fmt.Println(x,ok) fmt.Println("读取之后") } func main() { go write() read() fmt.Println("主线程") for { } }
-
读写并存,但是通道未关闭,如果写大于读,写协程阻塞,反之,读协程阻塞
var ( channel = make(chan int, 2) ) func write(){ fmt.Println("写之前") channel <- 1 channel <- 1 channel <- 1 channel <- 1 fmt.Println("写之后") } func read() { for { //当管道中没有值可以读取时,通道就会导致读协程阻塞,并不会到if中 x,ok := <- channel fmt.Println(x,ok) if !ok { fmt.Println("break") break } } } func main() { go write() go read() fmt.Println("主线程") for { } }
-
读写并存,但是通道关闭,写大于读,写协程阻塞,如果读大于写,运行完后就会退出读写协程
var ( channel = make(chan int, 2) ) func write(){ fmt.Println("写之前") channel <- 1 channel <- 1 channel <- 1 channel <- 1 //运行到这里的时候,channel会导致当前协程阻塞 fmt.Println("写之后") close(channel) } func read() { for { //管道关闭后,无法导致当前读协程阻塞,ok == flase 退出读协程 x,ok := <- channel fmt.Println(x,ok) if !ok { fmt.Println("break") break } } } func main() { go write() go read() fmt.Println("主线程") for { } }
例子
var (
channel = make(chan int, 10)
flag = make(chan bool, 1)
)
func writeData() {
defer close(channel)
for i := 0; i < 50; i++ {
channel <- i
fmt.Println("写入", i)
}
}
func readData() {
defer close(flag)
for {
val, ok := <-channel
if !ok {
break
}
fmt.Println("读取", val)
//time.Sleep(time.Millisecond * 400)
}
//写完后向一个管道内写入标志
flag <- true
}
func main() {
go writeData()
readData()
//通过一个channel阻塞主线程
for {
if _, ok := <-flag; ok {
break
}
time.Sleep(time.Second*2)
break
}
}
注意事项
-
channel
可以声明为只读, 或者只写, 默认双向通信func main() { //声明管道为只写 var chan1 chan<- int chan1 = make(chan int, 5) chan1 <- 1 //声明管道为只读 var chan2 <-chan int chan2 = make(chan int,5) <- chan2 }
案例
var ( flag = false ) //声明管道为只写 func send(ch chan<- int) { defer close(ch) for i := 0; i < 5; i++ { ch <- i } } //声明管道为只读 func read(ch <-chan int) { for { if val, ok := <-ch; ok { fmt.Println(val) } else { flag = true break } } } func main() { ch := make(chan int, 10) go send(ch) go read(ch) for !flag { } }
-
使用
select
可以解决管道数据阻塞func main() { intch := make(chan int, 10) for i := 0; i < 10; i++ { intch <- i } strch := make(chan string, 5) for i := 10; i < 15; i++ { strch <- string(i) } label: for { select { // 如果管道没有关闭,继续取值,不会一直阻塞, 会自动到下一个case匹配 case v := <-intch: fmt.Println("intch读取数据", v) case v := <-intch: fmt.Println("str读取数据", v) default: fmt.Println("没有匹配的值") break label } } }
-
使用recover, 解决协程中出现
panic
func error() { defer func() { if err:=recover();err !=nil { fmt.Println("协程发生错误",err) } }() slice := []int{} slice[0] = 1 } func correct(){ for i := 0; i < 20; i++ { fmt.Println("hello world") } } func main() { go correct() go error() for { } }