go通道chan:
通道(channel)是用来传递数据的一个数据结构,通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯,操作符 <- 用于指定通道的方向,发送或接收,如果未指定方向,则为双向通道。
1、无缓冲:
要求发送方和接收方的goroutine同时准备好,才能完成发送和接收操作。
如果两个goroutine都没有准备好,通道会导致先执行发送或者接收的goroutine阻塞等待,这种对通道进行发送跟接收的交互行为本身就是同步的。
2:有缓冲的通道:
缓冲信道的容量:
是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。
缓冲信道的长度:是指信道中当前排队的元素个数。
一种在被接收前能存储一个或多个值的通道。并不强制goroutine直接必须同时完成发送和接收,只有通道中没有要接收的值时,接收动作才会阻塞,只有在通道没有可用的缓冲区容纳被发送的值时,发送动作才会阻塞。
带缓冲通道在很多特性上和无缓冲通道是类似的。无缓冲通道可以看作是长度永远为 0 的带缓冲通道。因此根据这个特性,带缓冲通道在下面列举的情况下依然会发生阻塞:
1、带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
2、带缓冲通道为空时,尝试接收数据时发生阻塞。
channel存在3种状态:
- nil,未初始化的状态,只进行了声明,或者手动赋值为nil ;
- active,正常的channel,可读或者可写 ;
- closed,已关闭,千万不要误认为关闭channel后,channel的值是nil,关闭的状态的chan仍然可以读值(取值),但不能写值(会报panic: send on closed channel),nil状态的chan是不能close(panic: close of nil channel)。
func main() {
var a chan int
fmt.Println(a) // <nil>
a = make(chan int)
close(a)
fmt.Println(a) // 0xc00003e060
}
channel可进行3种操作:
1、读;
2、写;
3、关闭。
对于nil通道的情况,也并非完全遵循上表,有1个特殊场景:当nil的通道在select的某个case中时,这个case会阻塞,但不会造成死锁。
下面介绍使用channel的10种常用操作:
- 使用for range读channel
场景:当需要不断从channel读取数据时
原理:使用for-range读取channel,这样既安全又便利,**当channel关闭时,for循环会自动退出,**无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。
注意:只适合有缓冲chan
var syn sync.WaitGroup
var sss int
func main() {
sss = 2
syn.Add(1)
a := make(chan int, sss)
a <- 4
a <- 8
go Aaa(a)
syn.Wait()
}
func Aaa(a chan int) {
defer syn.Done()
for x := range a {
sss--
if sss == 0 {
close(a)
}
fmt.Println("==", x)
}
}
- 使用_,ok判断channel是否关闭
场景:读channel,但不确定channel是否关闭时
原理:读已关闭的channel会造成零值 ,如果不确定channel,需要使用ok进行检测。ok的结果和含义:
true
:读到数据,并且通道没有关闭。
false
:通道关闭,且无数据读到。
注意:只适合有缓冲chan
var syn sync.WaitGroup
var sss int
func main() {
sss = 2
syn.Add(1)
a := make(chan int, sss)
a <- 405
close(a)
go Aaa(a)
syn.Wait()
}
func Aaa(a chan int) {
defer syn.Done()
for {
v, ok := <-a
fmt.Println(v, ok)
if !ok {
break
}
}
}
输出:
405 true
0 false
3、读写
package main
import (
"fmt"
"time"
)
func WriteData(intChan chan int) {
for i:=1;i<=50;i++{
intChan<-i
fmt.Println("写:",i)
time.Sleep(time.Millisecond*100)
}
close(intChan) //写完后关闭管道
}
func ReadData(intChan chan int,exitChan chan bool) {
for{
v,ok:=<-intChan
if !ok{
fmt.Println("读取完毕")
break
}
fmt.Println("读:",v)
}
//设置全局标志 告诉main 读取完毕了 main主线程可以关闭了
exitChan<-true
close(exitChan)
}
func main() {
intChan :=make(chan int,50)
exitChan:=make(chan bool,1)
go WriteData(intChan)
go ReadData(intChan,exitChan)
//阻塞主线程
for ok:=range exitChan{
fmt.Println(ok)
}
}
4、管道实现互斥锁
var counter = 0
func Increase1000(id int, done chan bool, mutex chan bool) {
for i := 0; i < 1000; i++ {
mutex <- true //加锁
counter += 1
time.Sleep(time.Microsecond)
<-mutex //解锁
}
done <- true
}
func main() {
mutex, done := make(chan bool, 1), make(chan bool)
go Increase1000(1, done, mutex)
go Increase1000(2, done, mutex)
<-done
<-done
log.Println(counter)
}
5、管道实现定时通知
注意:有缓冲和无缓冲通道都可以实现
func Notice(d time.Duration, c chan bool) chan bool {
time.Sleep(d) //定时
c <- true
return c
}
func main() {
c := make(chan bool)
log.Println("one")
go Notice(time.Second, c)
<-c //管道没有写则阻塞
log.Println("tow")
go Notice(time.Second, c)
<-c
log.Println("three")
}
func Notice(d time.Duration) chan bool {
c := make(chan bool, 1)
time.Sleep(d) //定时
c <- true
close(c)
return c
}
func main() {
log.Println("one")
<-Notice(time.Second) //管道没有写则阻塞
log.Println("tow")
<-Notice(time.Second)
log.Println("three")
}
6、通过两个 goroutine 来计算数字之和
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{
7, 2, 8, -9, 4, 0, 8, 6, 78}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
今天的文章go 信道chan有缓冲通道跟无缓冲通道区别:分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/31329.html