Go语言中channel有关的知识点

本文深入探讨了Go语言中Channel的使用,包括不同容量Channel的特点,如何使用Range遍历Channel,以及Channel关闭后的注意事项。同时,文章详细介绍了Select语句在处理多个Channel时的应用,包括超时机制的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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需要注意如下几点:

  1. 如果向已经关闭的channel写数据就会导致panic: send on closed channel
  2. 从已经关闭的channel中读取数据是不会导致panic的,可以继续读取已经发送的数据,但如果已经发送的数据读取完成时继续读取,就会会读取到类型默认值或者零值;
  3. 如果通过range读取数据,channel关闭后就会跳出for循环;
  4. 如果重复再关闭已经关闭的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())

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值