Go学习笔记-通道高级操作

双向通道:既可以发也可以收的通道。

单向通道:只能发不能收,或者只能收不能发的通道

单向还是双向,由它的类型字面量体现的

 

双向示例:

var doubleChan = make(chan int, 1)

单向示例:

var uselessChan = make(chan<- int, 1)

声明并初始化了一个名叫uselessChan的变量。这个变量的类型是chan<- int,容量是1。

<-在 chan右边:只能发,不能收;即:发送通道。

<-在chan左边,只能收,不能发;即:接收通道;

 

单向通道作用:约束代码行为
 

func SendInt(ch chan<- int) {

ch <- rand.Intn(1000)

}

我们在调用SendInt函数的时候,只需要把一个元素类型匹配的双向通道传给它就行了,没必要用发送通道,因为 Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。

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

}

返回一个<-chan int类型的通道,调用getIntChan()的程序,只能从通道中接收元素值。

在函数类型中使用了单向通道,相等于在约束所有实现了这个函数类型的函数。

 

调用getIntChan的代码:

intChan2 := getIntChan()

for elem := range intChan2 {

fmt.Printf("The element in intChan2: %v\n", elem)

}

关于它的三件事

一、这样一条for语句会不断地尝试从intChan2种取出元素值,即使intChan2被关闭,它也会在取出所有剩余的元素值之后再结束执行。

二、当intChan2中没有元素值时,它会被阻塞在有for关键字的那一行,直到有新的元素值可取。

三、假设intChan2的值为nil,那么它会被永远阻塞在有for关键字的那一行。

这就是带range子句的for语句与通道的联用方式。不过,它是一种用途比较广泛的语句,还可以被用来从其他一些类型的值中获取元素。

 

select语句与通道联用

select语句只能与通道联用,一般由若干个分支组成。每次执行的时候,一般只有一个分支的代码会被执行。

每个case表达式中都只能包含操作通道的表达式,如接收表达式。

示例:

// 准备好几个通道。

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!")

}

select语句注意事项

1.如果像上述示例那样加入了默认分支,那么无论涉及通道操作的表达式是否有阻塞,select语句都不会被阻塞。如果那几个表达式都阻塞了,或者说都没有满足求值的条件,那么默认分支就会被选中并执行。

2.如果没有加入默认分支,那么一旦所有的case表达式都没有满足求值条件,那么select语句就会被阻塞。直到至少有一个case表达式满足条件为止。

我们可能会因为通道关闭了,而直接从通道接收到一个其元素类型的零值。所以,在很多时候,我们需要通过接收表达式的第二个结果值来判断通道是否已经关闭。一旦发现某个通道关闭了,我们就应该及时地屏蔽掉对应的分支或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。

3.select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个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 //立即结束当前的select语句的执行

}

fmt.Println("The candidate case is selected.")

}

select分支规则总结:

1.每个case表达式,都至少会包含一个代表发送操作的发送表达式或代表接收操作的接收表达式,也可能包含其它表达式。

2.候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的。select语句开始执行时,排在最上边的候选分支中的所有表达式都被求值完毕后,然后是它右边的表达式。依次顺序执行第二个,第三个等分支。

3.对于每一个case表达式,如果其中的发送表达式或接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值是不成功的,case分支不满足条件。

4.仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件,默认分支就会被执行。如果这时没有默认分支,那么select语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select语句(或者说它所在的 goroutine)就会被唤醒,这个候选分支就会被执行。

5.如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行。注意,即使select语句是在被唤醒时发现的这种情况,也会这样做。

6.一条select语句中只能够有一个默认分支。并且,默认分支只在无候选分支可选时才会被执行,这与它的编写位置无关。

7.select语句的每次执行,包括case表达式求值和分支选择,都是独立的。不过,至于它的执行是否是并发安全的,就要看其中的case表达式以及分支中,是否包含并发不安全的代码了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值