golang通道channel与定时器简介

Channel是Golang的核心类型,是goroutine间通讯的一种方式。

通道channel

channel基本使用示例:

  • make(chan TYPE) 创建通道
  • ch<- v 发送数据
  • v:=<-ch 接收数据
  • close(ch) 关闭通道
var ch chan int
ch = make(chan int)

go func() {
	for i := 0; i < 10; i += 1 {
		ch <- i + 1
		time.Sleep(time.Second)
	}
	close(ch)
}()

// v := <- ch // 读取一个
for v := range ch { // 读取所有,直到通道关闭
	fmt.Println(v)
}

原理

channel是go语言层面提供的协程间通讯方式;其由队列、类型信息、goroutine等待队列等组成;

type hchan struct {
	qcount   uint           // 当前队列中剩余元素个数
	dataqsiz uint           // 环形队列长度, 即可以存放的元素个数
	buf      unsafe.Pointer // 环形队列指针
	elemsize uint16         // 每个元素的大小
	closed   uint32         // 标识关闭状态
	elemtype *_type         // 元素类型
	sendx    uint           // 队列下标, 指示元素写入时存放到队列中的位置
	recvx    uint           // 队列下标, 指示元素从队列的该位置读出
	recvq    waitq          // 等待读消息的goroutine队列
	sendq    waitq          // 等待写消息的goroutine队列
	lock     mutex          // 互斥锁, chan不允许并发读写
}

chan内部通过一个环形队列作为缓冲区,长度在创建时指定。

等待队列:

  • 缓冲区为空或没有缓冲区(创建时长度为0),读取goroutine会被阻塞;会被写goroutine唤醒;
  • 缓冲区已满或没有缓冲区,写goroutine会被阻塞;会被读数据的goroutine唤醒;
  • 一个channel同时仅被允许一个goroutine读写;

创建/关闭

创建channel过程ch := make(Chantype, size int)实际是初始化hchan结构:容量(size)在创建时确定,以后不能修改(省略为0);size=0时为非缓冲channel。

关闭channel(close(ch))时会:

  • 把等待的读取goroutine(recvq上)全部唤醒,且数据设为“零值”(指针则为nil);从未初始化的通道(nil)接收元素会导致永久的阻塞。
  • 把等待的写goroutine(sendq上)全部唤醒,且产生panic

channel相关的panic还有:

  • 关闭值为nil的channel;
  • 关闭已经关闭的channel;
  • 向已关闭的channel写数据;

因此不要在接收端关闭通道(在发送端关闭)

select

select可监控多个channel:

  • 多个case满足条件时(可执行),执行顺序是随机的;
  • 若所有case分支都不能执行,会执行default分支(若有);
  • 若没有default,且全部case不可执行时,会阻塞;
  • 无论分支是否被选中执行,case关键字右边的表达式都会先求值(从左到右,自上而下)
ches := []chan int{
	make(chan int),
	make(chan int),
}
defer func() {
	for _, ch := range ches {
		close(ch)
	}
}()
numbers := []int{1, 2}
//go func() { <-ches[0] }()

getChan := func(n int) chan int {
	fmt.Println("Get chan: ", n)
	return ches[n]
}
getNum := func(n int) int {
	fmt.Println("Get num: ", n)
	return numbers[n]
}

select {
case getChan(0) <- getNum(0):
	fmt.Println("1st case selected")
case getChan(1) <- getNum(1):
	fmt.Println("2ed case selected")
default:
	fmt.Println("default selected")
}

// Get chan:  0
// Get num:  0     
// Get chan:  1    
// Get num:  1     
// default selected

// 若去掉<-ches[0]的注释(先等待读取数据),则输出为
// Get chan:  0
// Get num:  0     
// Get chan:  1    
// Get num:  1     
// 1st case selected

虽然两个case都没有被选中,但是右边表达式上的函数还是执行了。

range

range可持续从channel中读取数据(像遍历数组一样),无数据时会阻塞:

for v := range ch {
	fmt.Println("Value: ", v)
}

非阻塞读写

默认从通道中读取数据时(range或select),当无数据时会阻塞,若要不阻塞则需要使用带default的select:

非阻塞读

select {
case  n := <- ch:
	fmt.Println("Got:", n)
default:
	fmt.Println("no data")
}

非阻塞写

select {
case  ch <- n:
	fmt.Println("Set:", n)
default:
	fmt.Println("channel full")
}

清空

有时清空channel中已有的数据也是需要的:

for len(ch)>0{
	<-ch
}

单向channel

单向channe只是对channel的一种使用约束限制(一般用于参数传递):

  • <-chan int:只读channel,只能从中读取数据;
  • chan<- int:只写channel,只能写入数据;

定时器(time包)

time包中一些功能是用通道辅助实现的。

Timer一次性定时器

Timer是一种单一事件的定时器,即经过指定的时间后触发一个事件并结束。

  • time.NewTimer(d Duration) *Timer:创建定时器;
  • timer.Stop():停止定时器;
  • timer.Reset(d Duration):重置定时器(可用于修改还未到期的定时器;先停掉,再启动);
    Timer创建后便开始计时,时间到达后向Timer管道中发送当前时间(做为事件)。

简单接口:

  • After(d Duration) <-chan Time:创建定时器(为匿名定时器,不能对其操作,只能等待超时),并返回其管道;
  • AfterFunc(d Duration, f func()) *Timer:到期后,执行指定的函数。

超时示例接口:

ch := make(chan int, 1)
go func() {
	time.Sleep(time.Second)
	ch <- 1
}()

tmer := time.NewTimer(600 * time.Millisecond)
for i := 0; i < 3; i++ {
	select {
	case v := <-ch:
		fmt.Println("Received: ", v)
	case v := <-tmer.C: //<-time.After(600 * time.Millisecond):
		fmt.Println("Timeout: ", v)
		tmer.Reset(600 * time.Millisecond)
	}
}
fmt.Println("Stop: ", tmer.Stop())
	
// Timeout:  2022-01-13 08:57:36.4402573 +0800 CST m=+0.607663601
// Received:  1
// Timeout:  2022-01-13 08:57:37.0639077 +0800 CST m=+1.231328401
// Stop:  true

Ticker周期性定时器

Ticker是周期性定时器,即周期性的触发一个事件(事件触发时,若前面的事件还未被取走,则当前事件直接丢弃)。

  • time.NewTicker(d Duration) *Ticker:创建一个定时器;
  • ticker.Stop():停止定时器;

Ticker在使用完后务必要释放,否则会产生资源泄露(会持续消耗CPU资源)。

简单接口:

  • Tick(d Duration) <-chan Time:创建一个用不会停止的定时器(匿名的),并返回其管道;
ch := make(chan int, 1)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
go func(count int) {
	i := 0
	for _ = range ticker.C {
		select {
		case ch <- 0:
		case ch <- 1:
		}

		i++
		if i > count {
			break
		}
	}
	close(ch)
}(10)

for v := range ch {
	fmt.Print(v, " ")
}
fmt.Println()
	
// 0 1 1 0 0 1 1 1 0 0 0 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值