从0开始go语言-15|Go主题月

439 阅读5分钟

channel

继续肝channel相关知识!

channel基本用法

要用就的先声明:

普通int类型声明:

var v2 int

类型为int的channel声明:

var v2 chan int

声明一个map,元素是bool型的channel:

var m map[string] chan bool

定义一个channel,直接使用内置的函数make()即可:

ch := make(chan int)

在channel的用法,最常见的包括写入和读出。将一个数据写入(发送)至channel的语法,如下:

ch <- value

如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止,这有点像阻塞IO,接收数据一直等着,知道有数据准备好,我再接收数据。

select

在Unix中,通过调用select()函数来监控一系列的文件句柄,一旦其中一个句柄发生IO操作,该select调用就会被返回,该机制也用于实现高并发的Socket服务程序,Go语言直接在语言级别支持select关键字,用于处理异步IO问题。select的用法和switch语言类似,由select开始一个新的选择块,每个选择块的语句由case语句描述,select语句中限制了每个case语句必须有一个IO操作,如下:

select { 
  case <-chan1: 
     // 如果chan1成功读到数据,则进行该case处理语句
  case chan2 <- 1: 
     // 如果成功向chan2写入数据,则进行该case处理语句
  default: 
     // 如果上面都没有成功,则进入default处理流程
}

select不像switch,后面并不带判断条件,而是直接去查看case语句。每个case语句都必须是一个面向channel的操作。

缓冲机制

不带缓冲机制的channel,这种场景是在单个数据传递中可以接受,但由于现实中有很多高并发的场景,对于持续大量数据的传输channel需要缓冲,达到像消息队列一样的作用,达到写数据和取数据异步。需要创建带缓冲的channel如下:

a := make(chan int, 1024)

在调用make方法的时候多传入一个参数表示缓冲的大小,比如上文创建了一个大小是1024的int类型channel,写入的时候直到缓冲没有被填满之前都不会被阻塞。

从带缓冲的channel中读取数据可以使用与常规非缓冲channel完全一致的方法,但也可以使用range关键来实现更为简便的循环读取。如下:

for i := range c { 
     fmt.Println("Received:", i) 
}

超时机制

在并发编程的通信过程中,需要考虑超时问题,即向channel写数据时发现channel已满,或者从channel试图读取数据时发现channel为空。如果不正确处理这些情况,很可能会导致整个goroutine锁死。总的来说解决这种问题就是用一个参数设置超时的时间,一旦过了超时时间就释放掉资源,或者重试,重试再超时就立即终止并返回超时信息time out。

Go语言没有提供超时的机制,但可以利用select机制。因为select的特点是只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的情况,基于此特性,为channel实现超时机制如下:

// 首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1) 
go func() { 
     time.Sleep(1e9) // 等待1秒钟
     timeout <- true
}() 
// 做选择
select { 
     case <-ch: 
     // 从ch中读取到数据
     case <-timeout: 
     // 一直没有从ch中读取到数据,但从timeout中读取到了数据
}

意思一旦超时我们走超时的case条件下的业务,返回超时的数据,这种写法看起来是一个小技巧,但却是在Go语言开发中避免channel通信超时的最有效方法。在实际的开发过程中,这种写法也需要被合理利用起来,从而有效地提高代码质量。

channel的传递

在Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。可以利用channel可被传递的特性来实现我们的管道,假设我们传递的数据是一个整型

type PipeData struct { 
 value int
 handler func(int) int
 next chan int
}

然后写一个常规的处理函数。只要定义一系列PipeData的数据结构并一起传递给这个函数,就可以达到流式处理数据的目的。如下:

func handle(queue chan *PipeData) { 
 for data := range queue { 
     data.next <- data.handler(data.value) 
 } 
}

单向channel

单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。比如只能往这个channel写,或者只能从这个channel读。举例如下:

var ch1 chan int // ch1是一个正常的channel,不是单向的
var ch2 chan<- int// ch2是单向channel,只用于写int数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据

单向channel的意义就是多了一种channel用法,更多的是起到一种契约作用。

关闭channel

关闭:

close(ch)

判断是否关闭:

x, ok := <-ch

如果返回值是false则表示ch已经被关闭。

备注

先这样吧!channel传递还需要再理解下!

本文正在参与「掘金Golang主题学习月」, 点击查看活动详情