Go笔记(十四):通道 channel
Posted 无虑的小猪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go笔记(十四):通道 channel相关的知识,希望对你有一定的参考价值。
1、通道
通道channel是Go提供的一种用于各个协程(goroutine)之间的数据共享,保证数据同步交换的机制。协程是轻量级线程,类似于Java中的线程。
2、通道的类型
2.1、无缓冲通道
用于同步通信,可保证在发送和接收数据时完成两个goroutine(协程)的数据交换。
2.2、缓冲通道
用于异步通信,可接收到一个或多个值之前保存它们。通道中没有接收的值时,接收会阻塞;通道中没有可用的缓存区存放正在发送的值时,发送会阻塞。
3、通道的声明
声明通道是需指定数据类型,数据在通道上传递,任何给定时间只有一个goroutine可访问数据项,因此不会发生数据竞争。
通道由make函数创建,需指定chan关键字和通道的元素类型。
·创建无缓冲通道
// 字符串无缓冲通道 make(chan string)
·创建有缓冲通道
// 字符串有缓冲通道 make(chan string, 10)
使用内置函数make创建无缓冲和缓冲通道,make的第一个参数需要关键字chan,然后是通道允许交换的数据类型。
代码示例如下:
package main import ( "fmt" "math/rand" "time" ) // 创建string类型通道,只能传入int类型值 var vc = make(chan int) func send() // 生成随机数 rand.Seed(time.Now().UnixNano()) value := rand.Intn(10) fmt.Printf("send: %v\\n", value) // 睡眠1s time.Sleep(time.Second) // 将数据写入通道 vc <- value func main() // 关闭通道 defer close(vc) // 创建一个协程执行send go send() fmt.Println("wait...") // 将通道的值写入value变量中 value := <-vc fmt.Println("value : ", value) fmt.Println("send...")
执行结果如下:
4、通道接收及发送数据
1、通道接收数据
ch := make(chan string, 3) // 字符串缓冲通道 ch <- "hello" // 将数据写入通道
2、通道发送数据
data := <-通道变量名
示例代码如下:
package main import ( "fmt" "time" ) // 创建通道 var ch01 = make(chan string, 5) func main() // 关闭通道 defer close(ch01) go func() str := "hello" fmt.Printf("send: %v\\n", str) time.Sleep(time.Second) ch01 <- str () fmt.Println("wait...") // 将通道的值写入value变量中 value := <-ch01 fmt.Println("value : ", value) fmt.Println("over...")
执行结果如下:
5、通道的遍历
用for range遍历通道获取数据,若通道关闭,读多写少,没有了就是默认值;若没有关闭,会造成死锁。
package main import "fmt" func main() // 通道未关闭,读多写少,没有了就是默认值 ch01 := make(chan int) go func() // 关闭通道 defer close(ch01) // 写数据进入通道 for i := 0; i < 2; i++ ch01 <- i () for i := 0; i < 5; i++ data := <-ch01 fmt.Printf("ch01 data: %v\\n", data) fmt.Println("----------------------") // for rang遍历 ch02 := make(chan int) go func() // 关闭通道 defer close(ch02) // 写数据进入通道 for i := 0; i < 10; i++ ch02 <- i () for v := range ch02 fmt.Printf("ch02 date: %v\\n", v)
执行结果如下:
6、通道的发送和接收特性
1、对于同一个通道,发送操作之间是互斥的,接收操作也是互斥的;
2、发送和接收操作是原子的;
3、发送操作在完全完成之前会被阻塞。接收操作也是如此。
go语言之并发编程 channel
单向channel:
单向通道可分为发送通道和接收通道。但是无论哪一种单向通道,都不应该出现在变量的声明中,假如初始化了这样一个变量
var uselessChan chan <- int =make(chan <- int,10)
这样一个变量该如何使用呢,这样一个只进不出的通道没有什么实际意义。那么这种单向通道的应用场景在什么地方呢。我们可以用这种变换来约束对通道的使用方式。比如下面的这种声明
func Notify(c chan <- os.Signal, sig… os.Signal)
该函数的第一个参数的类型是发送通道类型。从参数的声明来看,调用它的程序应该传入一个只能发送而不能接收的通道。但是实际上应该传入的双向通道,Go会依据该参数的声明,自动把它转换成一个单向通道。Notify函数中的代码只能向通道c发送数据,而不能从它那里接收数据,在该函数中从通道c接收数据会导致编译错误。但是在函数之外不存在这个约束。
现在对SignalNotifier接口的声明稍作改变,如下:
type SignalNotifier interface{
Notify(sig…os.Signal) <- chan os.Signal
}
现在这个声明放在了函数外面,这种实现方法说明Notify方法的调用只能从作为结果的通道中接收元素值,而不能向其发送元素值。来看一个单向通道的例子
var strChan=make(chan string,3)
var mapChan=make(chan map[string]int,1)
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","e"}{
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{}{}
}
func main(){
syncChan1:=make(chan struct{},1)
syncChan2:=make(chan struct{},2)
go receive(strChan,syncChan1,syncChan2)
go send(strChan,syncChan1,syncChan2)
<-syncChan2
<-syncChan2
}
receive函数只能对strChan和syncChan1通道进行接收操作。而send函数只能对这2个通道进行发送操作。区别点在于chan 和 <-的位置。chan <- 表明是接收通道。<- chan表明是发送通道。运行结果如下:
sent: a [sender]
sent: b [sender]
sent: c [sender]
sent a sync signal.[sender]
Received a sync signal and wait a second...[receiver]
sent: d [sender]
Received: a [receiver]
Received: b [receiver]
Received: c [receiver]
Received: d [receiver]
Received: e [receiver]
sent: e [sender]
wait 2 seconds...[sender]
stopped.[receiver]
非缓冲channel
如果在初始化一个通道时将其容量设置为0或者直接忽略对容量的设置。就会使该通道变成一个非缓冲通道。和异步的方式不同,非缓冲通道只能同步的传递元素值
1 向此类通道发送元素值的操作会被阻塞。直到至少有一个针对通道的接收操作进行为止。该接收操作会首先得到元素的副本,然后再唤醒发送方所在的goroutine之后返回。也就是说,这是的接收操作会在对应的发送操作完成之前完成。
2 从此类通道接收元素值的操作会被阻塞,直到至少有一个针对该通道的发送操作进行为止。发送操作会直接把元素值赋值给接收方,然后再唤醒接收方所在的goroutine之后返回。这时的发送操作会在对应的接收操作之前完成。
func main(){
sendingInterval:=time.Second
receptionInterval:=time.Second*2
intChan:=make(chan int,0)
go func(){
var ts0,ts1 int64
for i:=1;i<=5;i++{
intChan <- i
ts1=time.Now().Unix()
if ts0 == 0{
fmt.Println("sent:",i)
}else{
fmt.Println("Sent:",i,"[interval:",ts1-ts0,"] ")
}
ts0=time.Now().Unix()
time.Sleep(sendingInterval)
}
close(intChan)
}()
var ts0,ts1 int64
Loop:
for{
select{
case v,ok:=<-intChan:
if !ok{
break Loop
}
ts1=time.Now().Unix()
if ts0 == 0{
fmt.Println("receive:",v)
}else{
fmt.Println("receive:",v,"[interval:",ts1-ts0,"] ")
}
}
ts0=time.Now().Unix()
time.Sleep(receptionInterval)
}
fmt.Println("End.")
}
运行结果:
sent: 1
receive: 1
receive: 2 [interval: 2 ]
Sent: 2 [interval: 2 ]
Sent: 3 [interval: 2 ]
receive: 3 [interval: 2 ]
receive: 4 [interval: 2 ]
Sent: 4 [interval: 2 ]
receive: 5 [interval: 2 ]
Sent: 5 [interval: 2 ]
End.
可以看到发送操作和接收操作都与receptioninterval的间隔一致。如果把sendingInterval改成time.Second*4. 则结果如下:发送操作和接收操作都与sendingInterval的间隔一致
sent: 1
receive: 1
Sent: 2 [interval: 4 ]
receive: 2 [interval: 4 ]
Sent: 3 [interval: 4 ]
receive: 3 [interval: 4 ]
Sent: 4 [interval: 4 ]
receive: 4 [interval: 4 ]
Sent: 5 [interval: 4 ]
receive: 5 [interval: 4 ]
End.
以上是关于Go笔记(十四):通道 channel的主要内容,如果未能解决你的问题,请参考以下文章
go语言学习笔记 — 进阶 — 并发编程:通道(channel) —— 各种各样的通道
go语言学习笔记 — 进阶 — 并发编程:通道(channel) —— 在多个goroutine之间通信的管道
go语言学习笔记 — 基础 — 高级数据类型 — 派生类型:指针(pointer)数据容器函数(func)结构体(struct)通道(channel)