单向通道
only_read := make(<-chan int,1) //只能发送通道
only_write := make(chan<- int, 1) //只能接受通道
问:单向通道的用途?
答:
单向通道的主要用途就是约束其他代码的行为。
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}
在实际场景中,这种约束一般会出现在接口类型声明中的某个方法定义上。 demo:
type Notifier interface{
SendInt(ch chan<- int)
}
在这里,Notifier接口中的SendInt方法只会接受一个发送通道作为参数,所以,在该接口中的所有实现类型中的SendInt 方法都会受到限制。这种约束方法还是很有用的,尤其是在我们编写模版代码或者可扩展程序库的时候。
顺便说一下,我们在调用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
}
函数getIntChane会返回一个 <-chan int 类型的通道,这就意味着得到该通道的程序,只能从通道中接受元素值。这实际上就是对函数调用方的一种约束了。
另外。我们在Go 语言中还可以声明函数类型,如果我们在函数类型中使用了单向通道,那么就相当于在约束所有实现了这个函数类型的函数。
intChan2 := getIntChane()
for elem := range intChan2{
fmt.Printf("The element int intChan2: %v\n", elem)
}
for range 遍历通道要注意的点:
1.for 语句会不断尝试从intChan2 中取出元素值,即使intChan2被关闭,它也会在取出所有剩余的元素值之后再结束关闭。
2.当 intChan2中没有元素值时,它会被阻塞在有 for关键字的那一行,直捣有新的元素可取。
3.假设intChan2的值为nil,那么它会被永远阻塞在有for关键字的那一行。
问:select语句与通道联用,应注意什么?
答:
// 通道数组
intChannels := [3]chan int {
make(chan int, 1),
make(chan int, 1),
make(chan int, 1),
}
// 随机选择一个通道,并向它发送元素子
index := rand.Int(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<-intChannels[2]:
fmt.Println("The third candidate case is selected.")
default:
fmt.Println("No candidate case is selected")
}
在使用select语句时,应该注意:
1. 如果加入了default分支,那么无论涉及通道操作的表达式是否有阻塞,select语句都不会被阻塞。如果那几个表达式被阻塞了,或者说都没有满足求值的条件,那么默认分支就会被选中并执行。
2. 如果没有加入默认分支,那么一旦所有的case表达式都没有满足求值条件,那么select语句就会被阻塞。直到至少有一个case表达式满足条件为止。
3. 我们可能会因为通道关闭了,而直接从通道接收到一个其元素类型的零值。所以,在很多时候,我们需要通过接受表达式的第二个结果值来判断通道是否关闭。一旦发现某个通道关闭了,我们就应该及时地屏蔽掉对应的分支或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。
4. select语句只能对其中的一个每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道时,就往往需要在for语句中嵌入select语句的方法实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。
错误示例:
intChan := make(chan int, 1)
// 1秒钟后关闭通道
time.AfterFunc(time.Second, func(){
close(intChan)
})
select {
case _, ok := <-intChan:
if !ok {
break;//错误,不能跳出for,只能跳出select
}
fmt.Println("The candidate case is selected.")
}
select分支选择规则细节:
1. select语句候选分支的case表达式 求值顺序是依次从代码的编写顺序从上到下的。排在最上方的候选分支中最左边的表达式会最先被求值,然后是它右边的表达式。然后依次顺序向下的候选分支开始求值。
2. 进当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足条件,那么默认分支就会被执行。如果这时没有默认分支,那么select分支语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select语句(或者说它所在的goroutine)就会被唤醒,这个候选分支就会被执行。
3. 如果select语句发现同时有多个候选分支满足选择条件,那么它会用一个伪随机的算法在这些分支中选择一个并执行。注意,即使select语句是在被唤醒时发现的这种情况,也会这么做。
4. select语句的每次执行,包括case表达式求值和分支选择,都是独立的,不过,至于它的执行是否是并发安全的,就要看其中的case表达式以及分支中,是否包含并发不安全的代码了。
问题: 1. 如果在select语句中发现某个通道已关闭,那么应该怎样屏蔽它所在的分支?
回答: 当判断表达式的第二个参数为false时,代表通道已经关闭,这时候将该channel赋值为nil,那么每次从该nil channel接受时就会阻塞,select会忽略阻塞的通道。
问题: 2.在select语句与for语句联用时,怎么直接退出最外层的for语句?
回答: 通过定义标签,配合goto或者breal语句能实现在同一个函数内任意跳转,就可以跳出多层循环了。