Channel使用

50 阅读3分钟

channel负责goroutine之间通信,channel的本质是一个队列,先进先出。

创建channel

有两种类型的channel

  • 创建有缓冲channel:make(chan int, 10)
  • 创建无缓冲channel:make(chan int)

从管道中读取,或者向管道写入数据,使用运算符:<-

  • ch <- 10
  • num := <- ch

无缓冲通道

无缓冲通道没有内部存储空间,数据的发送和接收必须同步完成,即写端与读端同时存在,写入一个数据,则能读出一个数据。

  • 发送方(send):必须等到接收方(receive)准备好接收数据时才能继续。
  • 接收方(receive):必须等到发送方发送数据时才能接收数据。 强调同步性

有缓冲通道

带缓冲通道在缓冲区未满时支持非阻塞写入,但一旦缓冲区写满,它的行为会变得类似无缓冲通道,也会导致写操作阻塞,直到缓冲区有空间可以接收新数据。相当于具备了异步的能力。

通道其他操作

关闭 close(ch)

不能再次发送数据,仍然可以读取数据 从通道中接收数据时,可以利用多返回值(ok)判断通道是否已经关闭 func main() {

func main() {
	ch := make(chan int, 10)
	go func(ch chan int) {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		close(ch)
	}(ch)
	for {
		if num, ok := <-ch; ok == true {
			fmt.Println("读到数据:", num)
		} else {
			break
		}
	}
} 

单向通道

可以将 channel 声明为只读或只写。

var sendOnly chan<- int = make(chan int)
sendOnly <- 42 // 只能发送

var receiveOnly <-chan int = make(chan int)
val := <-receiveOnly // 只能接收

应用场景

限制并发数量

例如限制为5,timeMore通过 <- 操作向通道中写入任务。如果通道已满,写入操作会阻塞,等待通道腾出空间。而main中创建了一个带缓冲的字符串通道 ch,缓冲区大小为 5,即最多允许 5 个任务同时注册到通道。

func timeMore(ch chan string) {
	// 执行前先注册,写不进去就会阻塞
	ch <- "任务"			
	fmt.Println("模拟耗时操作")
	time.Sleep(time.Second)	// 模拟耗时操作
	// 任务执行完毕,则管道中销毁一个任务
	<-ch
}

func main() {
	ch := make(chan string, 5)
	// 开启100个协程
	for i := 0; i < 100; i++ {
		go timeMore(ch)
	}
	for {
		time.Sleep(time.Second * 5)
	}
}

生产者消费者

设置一个带缓冲区的通道

  • ch := make(chan int, 5)
  • 生产者:向ch中写数据
  • 消费者:从ch中读数据

定时器

通道是定时器 time.Timer 的核心机制。 结构体定义:

type Timer struct {
    C <-chan Time // 一个只读的时间通道,用于接收定时器触发的时间值
    r runtimeTimer // 运行时定时器对象,隐藏实现细节
}
  • 当调用 time.NewTimer 创建定时器时,会初始化一个 C 通道。
  • 如果尝试读取通道 C,在定时时间到达前通道中没有数据,因此读取操作会阻塞。
  • 一旦定时时间到达,系统会自动向通道 C 中写入当前时间值,从而解除阻塞。