宝安三网合一网站建设,河北智能网站建设平台,wordpress多版,企业网站后台管理系统模板下载我们在说通道一般指的都是双向通道#xff0c;即#xff1a;既可以发也可以收的通道。
而单向通道就是#xff0c;只能发不能收#xff0c;或者只能收不能发的通道。一个通道是双向还是单向的是由它的类型字面量决定的。
比如#xff1a;
var uselessChan …我们在说通道一般指的都是双向通道即既可以发也可以收的通道。
而单向通道就是只能发不能收或者只能收不能发的通道。一个通道是双向还是单向的是由它的类型字面量决定的。
比如
var uselessChan make(chan- int,1)上面代码声明并初始化了一个名叫uselessChan的变量。这个变量的类型是chan- int容量是1。
请注意紧挨在关键字chan右边的那个-这表示了这个通道是单向的并且只能发而不能收。
类似的如果这个操作符紧挨在chan的左边那么就说明该通道只能收不能发。所以前者可以被简称为发送通道后者可以被简称为接收通道。
单向通道有什么应用价值
回答单向通道最主要的用途就是约束其他代码的行为。
问题分析
func SendInt(ch chan- int) {ch - rand.Intn(1000)
}我用func关键字声明了一个叫做SendInt的函数。这个函数只接受一个chan- int类型的参数。在这个函数中的代码只能向参数ch发送元素值而不能从它那里接收元素值。这就起到了约束函数行为的作用。
你可能会问我自己写的函数自己肯定能确定操作通道的方式为什么还要再约束好吧这个例子可能过于简单了。在实际场景中这种约束一般会出现在接口类型声明中的某个方法定义上。请看这个叫Notifier的接口类型声明
type Notifier interface {SendInt(ch chan- int)
}在接口类型声明的花括号中每一行都代表着一个方法的定义。接口中的方法定义与函数声明很类似但是只包含了方法名称、参数列表和结果列表。
一个类型如果想成为一个接口类型的实现类型那么就必须实现这个接口中定义的所有方法。因此如果我们在某个方法的定义中使用了单向通道类型那么就相当于在对它的所有实现做出约束。
在这里Notifier接口中的SendInt方法只会接受一个发送通道作为参数所以在该接口的所有实现类型中SendInt方法都会受到限制。这种约束方式还是很有用的尤其是在我们编写模板代码或者可扩展的程序库的时候。
顺便说一下我们在调用SendInt函数的时候只需要把一个元素类型匹配的双向通道传给它就行了没必要用发送通道因为 Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。
intChan1 : make(chan int, 3)
SendInt(intChan1)在另一个方面我们还可以在函数声明的结果列表中使用单向通道。如下所示
func getIntChan() -chan int {num : 5ch : make(chan int, num)for i : 0; i num; i {ch - i
}close(ch)return ch
}函数getIntChan会返回一个-chan int类型的通道这就意味着得到该通道的程序只能从通道中接收元素值。这实际上就是对函数调用方的一种约束了。 另外我们在 Go 语言中还可以声明函数类型如果我们在函数类型中使用了单向通道那么就相等于在约束所有实现了这个函数类型的函数。
调用getIntChan的代码
intChan2 : getIntChan()
for elem : range intChan2 {fmt.Printf(The element in intChan2: %v\n, elem)
}我把调用getIntChan得到的结果值赋给了变量intChan2然后用for语句循环地取出了该通道中的所有元素值并打印出来。 这里的for语句也可以被称为带有range子句的for语句。需要知道关于它的三件事。
一、这样一条for语句会不断地尝试从intChan2中取出元素值即使intChan2被关闭它也会在取出所有剩余的元素值之后再结束执行。
二、当intChan2中没有元素值时它会被阻塞在有for关键字的那一行直到有新的元素值可取。
三、假设intChan2的值为nil那么它会被永远阻塞在有for关键字的那一行。
select语句与通道怎样联用应该注意些什么
select语句只能与通道联用它一般由若干个分支组成。每次执行这种语句的时候一般只有一个分支中的代码会被运行。
select语句的分支分为两种一种叫做候选分支另一种叫做默认分支。候选分支总是以关键字case开头后跟一个case表达式和一个冒号然后我们可以从下一行开始写入当分支被选中时需要执行的语句。
默认分支其实就是 default case因为当且仅当没有候选分支被选中时它才会被执行所以它以关键字default开头并直接后跟一个冒号。同样的我们可以在default:的下一行写入要执行的语句。
由于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!)
}我先准备好了三个类型为chan int、容量为1的通道并把它们存入了一个叫做intChannels的数组。 然后我随机选择一个范围在 [0, 2] 的整数把它作为索引在上述数组中选择一个通道并向其中发送一个元素值。 最后我用一个包含了三个候选分支的select语句分别尝试从上述三个通道中接收元素值哪一个通道中有值哪一个对应的候选分支就会被执行。后面还有一个默认分支不过在这里它是不可能被选中的。
在使用select语句的时候我们首先需要注意下面几个事情。
如果像上述示例那样加入了默认分支那么无论涉及通道操作的表达式是否有阻塞select语句都不会被阻塞。如果那几个表达式都阻塞了或者说都没有满足求值的条件那么默认分支就会被选中并执行。如果没有加入默认分支那么一旦所有的case表达式都没有满足求值条件那么select语句就会被阻塞。直到至少有一个case表达式满足条件为止。还记得吗我们可能会因为通道关闭了而直接从通道接收到一个其元素类型的零值。所以在很多时候我们需要通过接收表达式的第二个结果值来判断通道是否已经关闭。一旦发现某个通道关闭了我们就应该及时地屏蔽掉对应的分支或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。select语句只能对其中的每一个case表达式各求值一次。所以如果我们想连续或定时地操作其中的通道的话就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意简单地在select语句的分支中使用break语句只能结束当前的select语句的执行而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。
select语句的分支选择规则都有哪些
对于每一个case表达式都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式同时也可能会包含其他的表达式。比如如果case表达式是包含了接收表达式的短变量声明时那么在赋值符号左边的就可以是一个或两个表达式不过此处的表达式的结果必须是可以被赋值的。当这样的case表达式被求值时它包含的多个表达式总会以从左到右的顺序被求值。select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值并且求值的顺序是依从代码编写的顺序从上到下的。结合上一条规则在select语句开始执行时排在最上边的候选分支中最左边的表达式会最先被求值然后是它右边的表达式。仅当最上边的候选分支中的所有表达式都被求值完毕后从上边数第二个候选分支中的表达式才会被求值顺序同样是从左到右然后是第三个候选分支、第四个候选分支以此类推。对于每一个case表达式如果其中的发送表达式或者接收表达式在被求值时相应的操作正处于阻塞状态那么对该case表达式的求值就是不成功的。在这种情况下我们可以说这个case表达式所在的候选分支是不满足选择条件的。仅当select语句中的所有case表达式都被求值完毕后它才会开始选择候选分支。这时候它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件那么默认分支就会被执行。如果这时没有默认分支那么select语句就会立即进入阻塞状态直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件select语句或者说它所在的 goroutine就会被唤醒这个候选分支就会被执行。如果select语句发现同时有多个候选分支满足选择条件那么它就会用一种伪随机的算法在这些分支中选择一个并执行。注意即使select语句是在被唤醒时发现的这种情况也会这样做。一条select语句中只能够有一个默认分支。并且默认分支只在无候选分支可选时才会被执行这与它的编写位置无关。select语句的每次执行包括case表达式求值和分支选择都是独立的。不过至于它的执行是否是并发安全的就要看其中的case表达式以及分支中是否包含并发不安全的代码了。
文章学习自郝林老师的《Go语言36讲》