单向通道可分为发送通道和接收通道。
需要注意的是,无论哪一种单向通道,都不应该出现在变量的声明中。
因为一个只进不出或只出不进的通道没有任何意义。
因此,单向通道应由双向通道变换而来。
我们利用这单向方式来约束程序对通道的使用方式。
// 声明发送通道作为方法入参,约束方法实现方,只能从参数通道中
// 发送元素值,而不能向其接收元素值。
func Notify(c chan<- os.Signal,sig ...os.Signal)
该函数第一个参数的类型是发送通道类型。
从表面上看,调用它的程序需要传入一个只能发送而不能接收的通道。
然而并不应该如此,在调用该函数时,
你应该传入一个双向通道。
Go会依据该参数的声明,自动把它转换为单向通道。
Notify函数中的代码只能向通道c发送元素值,而不能从它那里接收元素值。这是一个强约束。
函数中从通道c接收元素值会造成编译错误。
Notify函数中的代码只能对它进行发送操作,那么函数外的代码就只应对它进行接收操作。函数外的发送操作只会造成干扰。
这也是“代码即注释”编程风格的一种体现。
// 声明接收通道作为返回值,约束方法调用方,只能从结果的通道中
// 接受元素值,而不能向其发送元素值。
type SignalNotifier interface {
Notify(sig ...os.Signal) <-chan os.Signal
}
package main
import (
"fmt"
"time"
)
var strChan = make(chan string, 3)
func main() {
syncChan1 := make(chan struct{}, 1)
syncChan2 := make(chan struct{}, 2)
go receive(strChan, syncChan1, syncChan2) // 用于演示接收操作。
go send(strChan, syncChan1, syncChan2) // 用于演示发送操作。
<-syncChan2
<-syncChan2
}
func receive(strChan <-chan string,
syncChan1 <-chan struct{},
syncChan2 chan<- struct{}) {
<-syncChan1
fmt.Println("Received a sync signal and wait a second... [receiver]")
time.Sleep(time.Second)
for {
if elem, ok := <-strChan; ok {
fmt.Println("Received:", elem, "[receiver]")
} else {
break
}
}
fmt.Println("Stopped. [receiver]")
syncChan2 <- struct{}{}
}
func send(strChan chan<- string,
syncChan1 chan<- struct{},
syncChan2 chan<- struct{}) {
for _, elem := range []string{"a", "b", "c", "d"} {
strChan <- elem
fmt.Println("Sent:", elem, "[sender]")
if elem == "c" {
syncChan1 <- struct{}{}
fmt.Println("Sent a sync signal. [sender]")
}
}
fmt.Println("Wait 2 seconds... [sender]")
time.Sleep(time.Second * 2)
close(strChan)
syncChan2 <- struct{}{}
}
receive函数只能对strChan 和syncChan通道进行接收操作,而send函数只能对这两个通道进行发送操作。
另外,这两个函数都只能对syncChan2函数进行发送操作。
如果你试图在 receive 函数中关闭 strChan 通道,那么肯定不能通过编译,因为 Go不允许程序关闭接收通道。
(Cannot use ‘strChan’ (type <-chan string) as the type chan<- Type)
这与我在前面建议的不要在双向通道的接收端关闭通道的缘由一样。
已知,单向通道通常由双向通道转换而来。那么,单向通道是否可以转换回双向通道呢?答案是否定的。请记住,
通道允许的数据传递方向是channel类型的一部分。
对于两个通道类型而言,数据传递方向的不同就意味着它们类型的不同。