Golang:通道(channel)

双向通道

// 带缓冲区
ch1 := make(chan int, 10)

// 不带缓冲区
ch := make(chan int)

特性

  • 同一时刻,如果有多个goroutine尝试往同一个channel发送数据,那么只会执行其中一个,而不会同时发送;接收也是同理,同一时刻,有多个goroutine尝试从一个channel中接收数据,也只会执行其中一个,直到这个元素被完全移出,其他goroutine才能继续接收。
  • 向channel发送数据时,会复制元素,值类型就是复制副本,引用类型就是复制引用;从channel接收数据,也是复制,同时会删掉channel中的原值。处理元素时具有原子性
  • 发送操作包括复制元素值和放置副本到通道,接收操作包含复制通道内元素、放置副本到接收方、删除原值。这些步骤完成前,会一直阻塞。

关于阻塞

  • 缓冲通道,通道已满,所有发送都会阻塞,直到通道中有元素值被接收走。通道会优先通知`最早因此而等待的,那个发送操作所在的goroutine,后者会再次执行发送操作。由于发送操作被阻塞的goroutine会顺序进入通道内部的发送等待队列。
  • 如果通道已空,那么对它所有的接收操作都会阻塞,直到有新的元素值出现。通道会通知最早等待的、接收操作所在的goroutine,并使它再次执行接收操作。由于接收操作被阻塞的goroutine会顺序进入通道内部的接收等待序列。
  • 非缓冲通道,无论是发送操作还是接收操作,一开始就会被阻塞。是在用同步方式传递数据;非缓冲通道的数据是直接从发送方复制到接收方的,中间不会用通道做中转。
  • 值为nil的通道,发送和接收操作都会永久阻塞,所以一定记得初始化。

关于panic

  • 已经初始化、未关闭的通道。发送和接收都不会panic。
  • 已经初始化、关闭的通道。发送操作会panic。
  • 尝试关闭一个值为nil的通道,会引发panic。
  • 尝试关闭已经关闭的通道,会引发panic。
  • 接收操作可以感知到通道关闭,使用两个变量从通道中接收数据,第二个变量为false则表明通道被关闭,且没有元素可取。如果通道被关闭,但里面仍有数据,那么第二个变量为true。
  • 不应让接收方关闭通道。

单向通道

// 只能发不能收
ch1 := make(chan <- int, 1)

// 只能收不能发
ch2 := make(<- chan int, 1)

用途

  • 约束其他代码行为
  • 接口约束
// 约束所有要成为Notifier接口的类型,SendInt的实现都是只能发。
type Notifier interface {
  SendInt(ch chan<- int)
}
func SendInt(ch chan<- int) {
  ch <- rand.Intn(1000)
}


// 调用时双向通道做参数即可
intChan1 := make(chan int, 3)
SendInt(intChan1)
  • 迭代器
// 只能从返回通道接收数据
func getIntChan() <-chan int {
  num := 5
  ch := make(chan int, num)
  for i := 0; i < num; i++ {
    ch <- i
  }
  close(ch)
  return ch
}

intChan2 := getIntChan()
// 不断从channel中取值,即便关闭了也会取干净。
// 没有元素了会阻塞。不过这里getIntChan已经关闭了通道,所以取出所有元素后会结束。
// 如果返回的intChan2是nil,则for语句会阻塞。
for elem := range intChan2 {
  fmt.Printf("The element in intChan2: %v\n", elem)
}

Select语句

基本用法

// 准备好几个通道。
intChannels := [3]chan int{
  make(chan int, 1),
  make(chan int, 1),
  make(chan int, 1),
}
// 随机选择一个通道,并向它发送元素值。
index := rand.Intn(3)
fmt.Printf("The index: %d\n", index)
intChannels[index] <- index
// 哪一个通道中有可取的元素值,哪个对应的分支就会被执行。
select {
case <-intChannels[0]:
  fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
  fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]:
  fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default:
  fmt.Println("No candidate case is selected!")
}
  • 有default的情况,无论通道操作是否阻塞,select语句都不会阻塞。
  • 没有default,如果所有case都没有满足,则select语句会阻塞。
  • 从已经关闭的通道接收元素时,如果只使用一个变量,则会接收到元素类型的零值;最好使用两个变量,根据第二个变量值判断是否关闭(为true时可能关闭可能未关闭,为false时一定关闭)。
  • 如果想要连续操作,则需要在select语句外面套一层for循环;select语句分支中的break只能跳出select,不能跳出外层for循环

intChan := make(chan int, 1)
// 一秒后关闭通道。
time.AfterFunc(time.Second, func() {
  close(intChan)
})
select {
case _, ok := <-intChan:
  if !ok {
    fmt.Println("The candidate case is closed.")
    break
  }
  fmt.Println("The candidate case is selected.")
}

分支选择规则

  • 求值顺序从上到下,选择规则伪随机
var channels = [3]chan int{
	nil,
	make(chan int),
	nil,
}

var numbers = []int{1, 2, 3}

func main() {
	select {
	case getChan(0) <- getNumber(0):
		fmt.Println("The first candidate case is selected.")
	case getChan(1) <- getNumber(1):
		fmt.Println("The second candidate case is selected.")
	case getChan(2) <- getNumber(2):
		fmt.Println("The third candidate case is selected")
	default:
		fmt.Println("No candidate case is selected!")
	}
}

func getNumber(i int) int {
	fmt.Printf("numbers[%d]\n", i)
	return numbers[i]
}

func getChan(i int) chan int {
	fmt.Printf("channels[%d]\n", i)
	return channels[i]
}

channels[0]
numbers[0]
channels[1]
numbers[1]
channels[2]
numbers[2]
No candidate case is selected!
  • 每个case表达式,值都会从左到右被求值。如果求值时操作处于阻塞状态,则求值不成功,这个case不满足条件。
  • select语句包含的分支中的case表达式,会在select语句执行开始前先求值,顺序从上到下。
  • 求完所有case的值,开始选择候选分支,只会挑选满足条件的。如果没有满足条件的case,就会走default;没有default,就会进入阻塞状态,直到至少有一个分支满足条件。select语句会唤醒,去执行候选分支。
  • 如果发现同时有多个分支满足条件,就会使用伪随机算法,选择其中一个执行。被唤醒时也是如此。
  • 一个select语句只能有一个default,default的位置无影响。
  • select语句的每次执行、包括case表达式求值和分支选择都是独立的,不过这并不能保证它的并发安全,还得看case表达式和分支代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值