概述
为了并发的goroutines之间的通讯,golang使用了管道channel。你可以通过一个goroutines向channel发送数据,然后从另一个goroutine接收它
通常我们会使用make来创建channel ----- make(chan valType , [size])。
写入 channel c<-
读取channel
<-c
分类
一 无缓冲
make创建时忽略第二个参数即可创建无缓冲channel实例如下:
// csdnTest.go package main import ( "fmt" "time" ) func main() { messages := make(chan string) go func() { messages <- "hello" }() msg := <-messages fmt.Println(msg) }
值得注意的是:无缓冲channel只有接收和发送方都准备好了,channel才能正常工作,上述代码中如果我们把msg := <-messages这句注释掉,那么messages <- "hello"将会一直阻塞。
在这里做个猜想: 无缓冲的buffer只是直使用了ipc的pipe,pipe两端都打开时才能使用。“<-”被解释为打开写端,“->被解释为打开读端”
二 有缓冲
示例:
// csdnTest.go package main import ( "fmt" ) func main() { messages := make(chan string, 2) messages <- "hello" messages <- "world" fmt.Println(<-msg) fmt.Println(<-msg) }
说明:当创建有缓冲的buffer时,就不需要有准备好的接收器来接受数据,数据可以存在channel的缓冲区中,有缓冲的channel有如下特性:
1:数据未满可以写,当数据满了再写阻塞。
2:数据未空可以读,当数据空了再读阻塞。
通过上面的特性可以做个大胆的猜测,有缓冲的是通过msg-queue来实现的。
有缓冲和无缓冲从底层的实现就不同,因为他们使用了不同的ipc。
三 非阻塞
正常情况下的channel读和写都是阻塞的,然而我们可以使用一个带有default的select来触发非阻塞的channle。原本select也是阻塞的,在加入default时select本身也变为非阻塞
举个例子:
// csdnTest.go package main import "fmt" func main() { messages := make(chan string) signals := make(chan bool) //message和signals是无缓冲channel,因为接收器和发送器没同时准备好,所以channel无法使用。 //下面所有select会输出default的内容 select { case msg := <-messages: fmt.Println("received message", msg) default: fmt.Println("no message received") } msg := "hi" select { case messages <- msg: fmt.Println("sent message", msg) default: fmt.Println("no message sent") } select { case msg := <-messages: fmt.Println("received message", msg) case sig := <-signals: fmt.Println("received signal", sig) default: fmt.Println("no activity") } }
有兴趣同学将上面无缓冲的改成有缓冲的尝试一下。
至于select为何在加入default后channel和自身都未非阻塞了,你可以去看看go源码select.go这个源文件,-------------------也可以阅读我的下一期博客。
四 channel的用例
4.1 处理超时
c := make(chan string, 1) go func() { time.Sleep(time.Second * 3) c <- "inner goroutine" }() select { case res := <-c: fmt.Println(res) case <-time.After(time.Second * 1): fmt.Println("timeout") }
4.2 channel关闭
关闭一个channel时,就不能向该channel中写数据了。通常我们用一个值返回值来接收channel的数据,但此时可以用两个返回值 (valeu,b:=<-c) 来捕获channel是否关闭 b取值false或true。
当我们处理一个需要结束的任务,可采用下面的方式来调度
jobs := make(chan int, 10) done := make(chan bool) go func() { for { j, more := <-jobs if more { fmt.Println("received job", j) } else { fmt.Println("received all jobs") done <- true return } } }() for j := 1; j <= 3; j++ { jobs <- j fmt.Println("sent job", j) } close(jobs)
4.3 数据乒乓球式的流转
举一个golang官网比较知名的一个例子:
package main import "fmt" func ping(pings chan<- string, msg string) { pings <- msg } func pong(pings <-chan string, pongs chan<- string) { msg := <-pings pongs <- msg } func main() { pings := make(chan string, 1) pongs := make(chan string, 1) ping(pings, "passed message") pong(pings, pongs) fmt.Println(<-pongs) }
谢谢各位的阅读
有疑问加站长微信联系(非本文作者)