channel容量为0和为1的区别
- 容量为1的
channel
是有缓冲channel
的特殊情况,可以用在2个goroutine之间同步状态,或者其中一个等待另一个完成时才继续执行任务的情况。 - 无缓存的
channel
的容量始终为0,发送者发送数据和接受者接受数据时同时的,无任何中间态,不能缓冲任何数据。 - 容量为1的channel是可以缓冲1个数据,发送者和接受者之间可以不同时进行,可以发送者可以先把数据放进去,接受者可以过会儿再读取数据。无缓存的 channel 的发送者和接受者是相互等待,发送者等待接受者准备就绪才能发送数据,接受者等待发送者准备就绪才能接受数据,如果无缓存的 channel 在同一个协程中既发送又接受就会造成死锁而报错。
使用Range来遍历channel
- 使用
for range
来遍历channel,会自动等待channel的操作,一直到channel被关闭,退出循环。 - 第一个协程发送完数据之后关闭channel,使用range遍历读取channel中的数据,当channel被关闭后会退出循环结束程序
func main() {
//缓冲容量为3的channel
c := make(chan int64, 3)
//在协程中向channel中写数据
go func() {
for i := 0; i < 10; i++ {
c <- time.Now().UnixNano()
}
close(c)
}()
//通过range来打印数据,直到channel被关闭
go func() {
for i := range c {
fmt.Println(i)
}
}()
fmt.Scanln() //使用了fmt.Scanln()通过控制台输入扫描来hold住控制台,不让程序退出
fmt.Println("完成")
}
关闭Channel
关闭channel使用了内建的函数close,对于关闭channel需要注意如下几点:
- 如果向已经关闭的channel写数据就会导致
panic: send on closed channel
- 从已经关闭的channel中读取数据是不会导致panic的,可以继续读取已经发送的数据,但如果已经发送的数据读取完成时继续读取,就会会读取到类型默认值或者零值;
- 如果通过range读取数据,channel关闭后就会跳出for循环;
- 如果重复再关闭已经关闭的channel,也会导致panic。
select
- select 可以等待和处理多个通道。使用select可以在case语句中选择一组channel中未阻塞的channel。
- select只会执行一次不会循环,只会选择一个case来处理,如果要一直处理channel,通常要结合一个无限for循环一起来使用
- 在
default case
存在的情况下,如果没有case需要处理,则会选择default去处理;如果没有default case,则select语句会阻塞,直到某个case需要处理。 - 如果在使用
for 无限循环+select
来操作多个channel
,当channel
被关闭后,会一直读取类型默认值,这样会导致进入无限死循环
import "time"
import "fmt"
func main() {
//创建2个channel
c1 := make(chan string)
c2 := make(chan string)
//通过2个协程分别往2个channel中写数据
go func() {
for i := 0; i < 9; i++ {
time.Sleep(100 * time.Millisecond)
c1 <- fmt.Sprintf("c1: %d", i+1)
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
c2 <- fmt.Sprintf("c2: %d", i+1)
}
close(c2)
}()
//通过1个协程从2个channel中读取数据,如果没有数据则阻塞
go func() {
for {
select { // 死循环
case msg1 := <-c1:
fmt.Printf("received %d: %s \n", time.Now().Unix(), msg1)
case msg2 := <-c2:
fmt.Printf("received %d: %s \n", time.Now().Unix(), msg2)
}
}
}()
fmt.Scanln()
}
对于读取已经关闭的channel时,可以使用返回值来判断channel是否被关闭,下面的例子中如果返回的ok为false,就说明channel已经被关闭:
i, ok := <-c
if !ok {
//channel已经被关闭
// 可以在这里退出死循环
}
Select超时
在select中可以处理超时,超时在处理外部资源或需要绑定执行时间的程序非常重要,通过channel和select,在Go中可以很容易且优雅的实现超时机制。在下面的例子使用一个协程来模拟任务处理,利用sleep 5秒钟来模拟任务执行时间,任务完成后向channel中写入结果;在select语句中实现超时,第一个case来读取channel中的数据,等待结果写入;第二个channel中使用time.After(3 * time.Second)来等待3秒超时时间。由于实际任务执行时间是5秒钟,超时时间是3秒钟,所以等待3秒钟后,time.After返回的channel中会写入一个时间,select语句就选择第二个case执行,然后结束程序:
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
fmt.Println("开始时间", time.Now().Unix())
time.Sleep(5 * time.Second)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(3 * time.Second):
fmt.Println("timeout 3")
}
fmt.Println("完成时间:", time.Now().Unix())
}