Go channel
Channel 是golang中的管道类型,并且拥有FIFO的特性,经常通过它来实现并发中的数据通信。
声明与创建
// Chan 声明需要指定类型,此chan只能传递int类型数据
var ch chan int
// 创建
ch = make(chan int)
// Chan 创建, 和其他类型一样,也可以使用':='在创建时声明
ch := make(chan int)
ch2 := make(chan interface{})
数据传递
Chan的数据传递需要使用’<-’, ‘<-chan’ 从此chan接收数据, ‘chan<-’ 发送数据到此chan。
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}
缓存与阻塞
// 可以在创建时指定chan的缓存大小,默认没有缓存
ch := make(chan int, 100)
如果没有缓存,那么接收方会一直阻塞,直到有数据发送。
package main
import "time"
func main() {
ch := make(chan int)
go func() {
time.Sleep(time.Second)
ch <- 1
}()
<-ch // 这里阻塞一秒
}
如果没有缓存,那么发送方会一直阻塞,直到发送的数据被接收。
package main
import "time"
func main() {
ch := make(chan int)
go func() {
time.Sleep(time.Second)
<-ch
}()
ch <- 1 // 这里阻塞一秒
}
如果使用缓存,那么在缓存没有满时,发送方不会阻塞,缓存已满,发送方会阻塞
package main
import "time"
func main() {
ch := make(chan int, 5)
go func() {
for i := 0; i < 6; i += 1 {
time.Sleep(time.Second)
<-ch
}
}()
// 前五次不会阻塞,只有最有一次阻塞,阻塞一秒
for i := 0; i < 6; i += 1 {
ch <- i
}
}
如果使用缓存,那么在缓存不为空时,接收方不会阻塞,缓存为空,接收方会阻塞
package main
import "time"
func main() {
ch := make(chan int, 5)
go func() {
for i := 0; i < 6; i += 1 {
time.Sleep(time.Second)
ch <- i
}
}()
// 每次接收消息都在阻塞,阻塞六次
for i := 0; i < 6; i += 1 {
<-ch
}
}
空channel
向空chan发送数据,和接收数据,会一直阻塞
Close
close()函数可以关闭一个chan,向已关闭的chan发送数据进程会panic,从已关闭的chan接收数据会立即返回,返回未接收的消息,如果没有未接收的消息,返回类型的0值。
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
close(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
ch <- 2
}
/*
1
0
panic: send on closed channel
goroutine 1 [running]:
main.main()
/home/zhangsicong/golang/shenjing/shenjing_be/server/test.go:13 +0x19f
exit status 2
*/
从chan接收数据时,可以接收额外的值来判断chan有没有被关闭
例如:n, ok := <- ch, 如果关闭,ok为false
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 3; i += 1 {
ch <- i
}
close(ch)
}()
time.Sleep(time.Second)
for i := 0; i < 5; i += 1 {
n, ok := <-ch
fmt.Println(n, ok)
}
}
/*
1 true
2 true
3 true
0 false
0 false
*/
for range可以读取channel
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i += 1 {
ch <- i
}
close(ch) // 如果没有close, for range将会一直阻塞下去
}()
for n := range ch {
fmt.Println(n)
}
}
Select
Select会选择任一可操作的chan,如果没有chan可操作,如果存在default,将执行其中的代码,否则将阻塞。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
done := make(chan bool)
go func() {
for i := 0; i < 10; i += 1 {
ch <- i
}
time.Sleep(2 * time.Second)
done <- true
}()
for {
select {
case n := <-ch:
fmt.Println("Recv", n)
case <-done:
fmt.Println("Done")
return
default:
fmt.Println("Wait")
}
time.Sleep(100 * time.Millisecond)
}
}
/*
Wait
Recv 0
Recv 1
Recv 2
Recv 3
Recv 4
Recv 5
Recv 6
Recv 7
Recv 8
Recv 9
Wait
Wait
Wait
Wait
...
Done
*/
定时与channel
package main
import (
"fmt"
"time"
)
func main() {
// 过期返回一次, timeAfter是 chan time.Time
timeAfter := time.After(100 * time.Millisecond)
// 过期返回一次, timer.C是 chan time.Time
timer := time.NewTimer(100 * time.Millisecond)
// 定期返回
ticker := time.NewTicker(100 * time.Millisecond)
for i := 0; i < 10; i += 1 {
select {
case <-timeAfter:
fmt.Println("Time after1")
case <-timer.C:
fmt.Println("Timer")
case <-ticker.C:
fmt.Println("Ticker")
}
}
}
/*
Time after1
Timer
Ticker
Ticker
Ticker
Ticker
Ticker
Ticker
Ticker
Ticker
*/

本文详细介绍了Golang中的Channel,包括声明与创建、数据传递、缓存与阻塞、空channel、Close操作、接收判断、for-range读取以及Select的使用。通过实例展示了Channel在并发中的数据通信和同步机制,阐述了其工作原理和注意事项。

被折叠的 条评论
为什么被折叠?



