全栈编程

Balance $ 2,317
Item Sold 1230
文章作者: 全栈编程@luboke.com
版权声明: 本文章为go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!

channel快速入门

channel用于goroutines之间的通信,让它们之间可以进行数据交换。像管道一样,一个goroutine_A向channel_A中放数据,另一个goroutine_B从channel_A取数据。

channel是指针类型的数据类型,通过make来分配内存。例如: 

ch := make(chan int)

这表示创建一个channel,这个channel中只能保存int类型的数据。也就是说一端只能向此channel中放进int类型的值,另一端只能从此channel中读出int类型的值。

需要注意,chan TYPE才表示channel的类型。所以其作为参数或返回值时,需指定为xxx chan int类似的格式。

向ch这个channel放数据的操作形式为:

ch <- VALUE


从ch这个channel读数据的操作形式为:

<-ch             // 从ch中读取一个值
val = <-ch
val := <-ch      // 从ch中读取一个值并保存到val变量中
val,ok = <-ch    // 从ch读取一个值,判断是否读取成功,如果成功则保存到val变量中

当ch出现在<-的左边表示send,当ch出现在<-的右边表示recv。

当ch出现在<-的左边表示send,当ch出现在<-的右边表示recv。

一个goroutine用于执行sender()函数,该函数每次向channel ch中发送一个字符串。
另一个goroutine用于执行recver()函数,该函数每次从channel ch中读取一个字符串。

注意上面的recv = <-ch,当channel中没有数据可读时,recver goroutine将会阻塞在此行。由于recver中读取channel的操作放在了无限for循环中,表示recver goroutine将一直阻塞,直到从channel ch中读取到数据,读取到数据后进入下一轮循环由被阻塞在recv = <-ch上。直到main中的time.Sleep()指定的时间到了,main程序终止,所有的goroutine将全部被强制终止。

因为receiver要不断从channel中读取可能存在的数据,所以receiver一般都使用一个无限循环来读取channel,避免sender发送的数据被丢弃。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)
	go sender(ch)         // sender goroutine
	go recver(ch)         // recver goroutine

	//注释就不会执行
	time.Sleep(1e9)
}

func sender(ch chan string) {
	ch <- "aa"
	ch <- "bb"
	ch <- "cc"
	ch <- "dd"
}

func recver(ch chan string) {
	var recv string
	for {
		recv = <-ch
		fmt.Println(recv)
	}
}

channel 操作

每个channel都有3种操作:send、receive和close

send:表示sender端的goroutine向channel中投放数据
receive:表示receiver端的goroutine从channel中读取数据
close:表示关闭channel
关闭channel后,send操作将导致painc
关闭channel后,recv操作将返回对应类型的0值以及一个状态码false
close并非强制需要使用close(ch)来关闭channel,在某些时候可以自动被关闭
如果使用close(),建议条件允许的情况下加上defer
只在sender端上显式使用close()关闭channel。因为关闭通道意味着没有数据再需要发送

 

//判断channel是否被关闭:
val, ok := <-counter
if ok {
    fmt.Println(val)
}

unbuffered channel和buffered channel

  • unbuffered channel:阻塞、同步模式
    • sender端向channel中send一个数据,然后阻塞,直到receiver端将此数据receive
    • receiver端一直阻塞,直到sender端向channel发送了一个数据
  • buffered channel:非阻塞、异步模式
    • sender端可以向channel中send多个数据(只要channel容量未满),容量满之前不会阻塞
    • receiver端按照队列的方式(FIFO,先进先出)从buffered channel中按序receive其中数据

可以认为阻塞和不阻塞是由channel控制的,无论是send还是recv操作,都是在向channel发送请求

  • 对于unbuffered channel,sender发送一个数据,channel暂时不会向sender的请求返回ok消息,而是等到receiver准备接收channel数据了,channel才会向sender和receiver双方发送ok消息。在sender和receiver接收到ok消息之前,两者一直处于阻塞。

使用无缓冲通道在goroutine之间同步

  • 对于buffered channel,sender每发送一个数据,只要channel容量未满,channel都会向sender的请求直接返回一个ok消息,使得sender不会阻塞,直到channel容量已满,channel不会向sender返回ok,于是sender被阻塞。对于receiver也一样,只要channel非空,receiver每次请求channel时,channel都会向其返回ok消息,直到channel为空,channel不会返回ok消息,receiver被阻塞。

使用有缓冲通道在goroutine之间同步

buffered channel的两个属性

buffered channel有两个属性:容量和长度:和slice的capacity和length的概念是一样的

  • capacity:表示bufffered channel最多可以缓冲多少个数据
  • length:表示buffered channel当前已缓冲多少个数据
  • 创建buffered channel的方式为make(chan TYPE,CAP)

unbuffered channel可以认为是容量为0的buffered channel,所以每发送一个数据就被阻塞。注意,不是容量为1的buffered channel,因为容量为1的channel,是在channel中已有一个数据,并发送第二个数据的时候才被阻塞。

换句话说,send被阻塞的时候,其实是没有发送成功的,只有被另一端读走一个数据之后才算是send成功。对于unbuffered channel来说,这是send/recv的同步模式。而buffered channel则是在每次发送数据到通道的时候,(通道)都向发送者返回一个消息,容量未满的时候返回成功的消息,发送者因此而不会阻塞,容量已满的时候因为已满而迟迟不返回消息,使得发送者被阻塞

实际上,当向一个channel进行send的时候,先关闭了channel,再读取channel时会发现错误在send,而不是recv。它会提示向已经关闭了的channel发送数据。

package main

import "fmt"

func main() {
	counter := make(chan int)
	go func() {
		counter <- 32
	}()
	close(counter)
	fmt.Println(<-counter)
}

所以,在Go的内部行为中,send和recv是一个整体行为,数据未读就表示未send成功

文章作者: 全栈编程@luboke.com
版权声明: 本文章为go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!
copyright © 2020 全栈编程@luboke.com