golang 之select 的高级用法

4,790 阅读2分钟

select有3个特色分别是:

  • nil的通道永远阻塞
  • 如何跳出for -select 循环
  • select{} 永远阻塞

nil通道永远阻塞

当case上读取一个通道时,如果这个通道是 nil , 则该case 永远阻塞,这个特性我们可以自定义将某个信道强制进行阻塞

    func CombineChannel(ch1,ch2 chan int) <-chan int {
	out := make(chan int,3)
	go func() {
		defer close(out)
		for {
			select {
				case v1 , ok := <-ch1:
					if !ok {
						ch1 = nil
						continue
					}
					out <- v1
				case v2 , ok := <-ch2:
					if !ok {
						ch2 = nil
						continue
					}
					out <- v2

			}

			if ch1 == nil && ch2 == nil {
				break
			}
		}
	}()
	return out
}

发送者可以通过close 关闭一个信道,接受者可以按如下方式来接收:

//如果信道被关闭或者指向了nil , 这 OK 就是  false

v  ,  ok  = <-c

跳出for-select 循环

在select 中的break 并不能跳出for-select 循环 。 既然break 不能跳出for-select 循环,我们可以另辟蹊径去跳出,总共有3招

  • 在满足条件的case内 , 使用return 结束协程
  • 在select 外for内使用break跳出
  • 使用goto , goto 只要你不乱定义,适当使用还是不错的
package selectapply

import (
	"fmt"
	"time"
)

func addX(c chan int,s chan string) {
	for i:=0;i<10;i++ {
		time.Sleep(time.Second)
		c <-i
	}
	s <- "a"
}

func addY(c chan int,s chan string) {
	for i:=0;i<20;i++ {
		time.Sleep(time.Second)
		c <-i
	}
	s <- "b"
}

func SelectBaisc() {
	c1 := make(chan int)
	c2 := make(chan int)

	sigle := make(chan string,2)

	go addX(c1 , sigle)

	go addY(c2,sigle)

	var nums []string

	for {
		select {
		case t1 := <-c1:
			fmt.Println("addX :", t1)
		case t2 := <-c2:
			fmt.Println("addY :", t2)
		case t3 := <-sigle:
			p("接收到了信号")
			nums = append(nums,t3)
			//下面这种写法,nums会进行重新赋值,而不会累加
			//nums := append(nums,t3)
			p("nums :",nums,"****length****",len(nums))
			if len(nums) == 2{
				goto TAG
			}
		}
	}
	TAG: return
}

select{} 永远阻塞

select{} === ch := make(chan int)

永远阻塞的作用在哪里呢? 当我们开发一个程序的时候,main函数千万不能在子协程干完活前退出,不然所有的子协程就都被迫退出了。服务也就被迫停止了。