方法一:make(chan Type)等价于make(chan Type,0)其中 Type 代表数据类型。
方法二:make(chan Type,capacity)
二、channel的使用写入 channel 方法:channel <- value
从 channe 中读:<-channel 接收并将其丢弃
x := <-channel 接收并赋值给 x
x ,ok := <-channel 接收并赋值给 x,同时检查通道是否已关闭或者是否为空
Channel通道在使用的时候,有以下几个注意点:
1.用于goroutine,传递消息的。
2.通道,每个都有相关联的数据类型, nil chan,不能使用,类似于nil map,不能直接存储键值对
3.使用通道传递数据:<- chan <- data,发送数据到通道。向通道中写数据 data <- chan,从通道中获取数据。从通道中读数据
4.阻塞: 发送数据:chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞 读取数据:data <- chan,也是阻塞的。直到另一条goroutine,写出数据解除阻塞。
5.本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作。
最后:通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中。
看一段代码吧:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
defer fmt.Println("goroution 结束")
fmt.Println("goroutio 正在运行。。。。")
c <- 666 //将666发送个c
}()
m := <-c
fmt.Println("num=", m)
fmt.Println("main goroution 结束。。。")
}
三、channel有缓冲与无缓冲同步问题
首先来看一下有缓冲的channel运行的过程吧,如下图
那么无缓冲的会是什么过程呢?接着来看下一图
上面的一段代码是无缓冲的,之前学习的所有通道基本上都没有缓冲。发送和接收到一个未缓冲的通道是阻塞的。一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前都是阻塞的。同样的,对于接收来讲,在另一个goroutine发送之前,它也是阻塞的。
那么有缓冲的该怎么写的呢?
缓冲通道就是指一个通道,带有一个缓冲区。发送到一个缓冲通道只有在缓冲区满时才被阻塞。类似地,从缓冲通道接收的信息只有在缓冲区为空时才会被阻塞。
可以通过将额外的容量参数传递给make函数来创建缓冲通道,该函数指定缓冲区的大小。
语法:
ch := make(chan type, capacity)
上述语法的容量应该大于0,以便通道具有缓冲区。默认情况下,无缓冲通道的容量为0,因此在之前创建通道时省略了容量参数。
我们来看下一段代码:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) //带有缓冲的
fmt.Println("len(c)=", len(c), ",cpa(c)=", cap(c))
go func() {
defer fmt.Println("子go 结束")
for i := 0; i < 4; i++ {
c <- i
fmt.Println("子go程正在运行,发送的元素=", i, "len(c)=", len(c), ",cpa(c)=", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("main go 结束。。。")
}
为了方便我们看是否有缓冲,我加了这样一句 time.Sleep(2 * time.Second) 目的是让子go中全部放入缓冲中之后,main go再去拿。然而如果我将子 go 中的循环条件 i 变成 i<4,那么我们会发现channel 会有个等待的过程。
由此可见channel的特点:(前提channel没有被关闭)
1.当 channel 已经满,再向里面写数据,就会阻塞。
2.当 channel 为空,从里面取数据也会阻塞。
四、channel关闭我们看一下这段代码:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//close(c) //关闭
}()
for {
if data, ok := <-c; ok { //如果channel没有关闭,那么ok为true,否则为false
fmt.Println(data)
} else {
break
}
}
fmt.Println("Finishede...")
}
如果不关闭的话,就会出现死锁。因为子 go 中都已放入暖冲后没有可用的供给 main go就会导致main go一直等待。
总结:
- channel 不像文件一样需要经常关闭,只有当你确实没有任何发送数据了,或者你想显式结束range循环之类的,才去关闭 channel。
- 关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
- 关闭 channel 后,可以继续从 channel 接收数据;
- 对于 nil channel,无论收发都会阻塞。(即没有使用 make()初始化)
上一段代码中的 for 循环可以用 for range 代替,range 可以用来迭代不断操作 channel ,如下面代码:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c) //关闭
}()
// for {
// if data, ok := <-c; ok { //如果channel没有关闭,那么ok为true,否则为false
// fmt.Println(data)
// } else {
// break
// }
// }
//可以使用range来迭代不断操作channel
for data := range c {
fmt.Println(data)
}
fmt.Println("Finishede...")
}
六、channel 与 select
单流程下一个 go 只能监控一个 channel 的状态,select 可以完成监控多个 channel 的状态。
select语句类型于switch语句
但是select语句会随机执行一个可运行的case
如果没有case可以运行,要看是否有default,如果有就执行default,否则就进入阻塞,直到有case可以运行
说明:
每个case都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果有多个case都可以运行,select会随机公平地选出一个执行。其他不会执行。
否则:
如果有default子句,则执行该语句。
如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
看下面一段代码:
package main
import (
"fmt"
)
func fibonacil(c, quite chan int) {
x, y := 1, 1
for {
select {
case c <- x:
//如果c可写,则会运行该case
x = y
y = x + y
case <-quite:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quite := make(chan int)
go func() {
for i := 0; i < 6; i++ {
fmt.Println(<-c)
}
quite <- 0
}()
fibonacil(c, quite)
}
七、双向通道
通道,channel,是用于实现goroutine之间的通信的。一个goroutine可以向通道中发送数据,另一条goroutine可以从该通道中获取数据。截止到现在我们所学习的通道,都是既可以发送数据,也可以读取数据,我们又把这种通道叫做双向通道。
双向定义方法:
ch1 := make(chan int)//双向,读,写
八、单向通道
单向通道,也就是定向通道。
之前我们学习的通道都是双向通道,我们可以通过这些通道接收或者发送数据。我们也可以创建单向通道,这些通道只能发送或者接收数据。
单向:定向
chan <- T,
只支持写,
<- chan T,
只读
单向定义方法 :
data := <- a // read from channel a
a <- data // write to channel a
ch2 := make(chan <- int) // 单向,只写,不能读
ch3 := make(<- chan int) //单向,只读,不能写
实例代码:
package main
import "fmt"
func main() {
/*
双向:
chan T -->
chan <- data,写出数据,写
data <- chan,获取数据,读
单向:定向
chan <- T,
只支持写,
<- chan T,
只读
*/
ch1 := make(chan string) // 双向,可读,可写
done := make(chan bool)
go sendData(ch1, done)
data :=<- ch1 //阻塞
fmt.Println("子goroutine传来:", data)
ch1 <- "我是main。。" // 阻塞
<-done
fmt.Println("main...over....")
}
//子goroutine-->写数据到ch1通道中
//main goroutine-->从ch1通道中取
func sendData(ch1 chan string, done chan bool) {
ch1 <- "我是小明"// 阻塞
data := <-ch1 // 阻塞
fmt.Println("main goroutine传来:",data)
done <- true
}
九、time包中的通道相关函数
主要就是定时器,标准库中的Timer让用户可以定义自己的超时逻辑,尤其是在应对select处理多个channel的超时、单channel读写的超时等情形时尤为方便。
1.time.NewTimer
Timer是一次性的时间触发事件,这点与Ticker不同,Ticker是按一定时间间隔持续触发时间事件。
Timer常见的创建方式:(创建一个计时器,d时间以后触发)
func NewTimer(d Duration) *Timer
timer := time.NewTimer(3 *time.Second)//这里数字就是定义阻塞多少秒
fmt.Printf("%T\n",timer) //*time.Timer
fmt.Println(time.Now()) //2022-07-31 08:08:38.3434183 +0800 CST m=+0.002415501
//此处等待channel中的数值,会阻塞3秒
ch2 := timer.C
fmt.Println(<-ch2 ) //2022-07-31 08:09:56.1214098 +0800 CST m=+3.011776701
- 用于在指定的Duration类型时间后调用函数或计算表达式。
- 如果只是想指定时间之后执行,使用time.Sleep()
- 使用NewTimer(),可以返回的Timer类型在计时器到期之前,取消该计时器
- 直到使用<-timer.C发送一个值,该计时器才会过期
2.timer.Stop
示例代码:
//新建一个计时器
timer2 := time.NewTimer(5*time.Second)
//开始goroutine,来处理触发后的事件
go func() {
<- timer2.C
fmt.Println("Timer 2 结束了。。。开始。。。。")
}()
//由于上面的等待信号是在新线程中,所以代码会继续往下执行,停掉计时器
time.Sleep(3*time.Second)
flag := timer2.Stop()
if flag{
fmt.Println("Timer 2 停止了。。。")
}
3.time.After()
在等待持续时间之后,然后在返回的通道上发送当前时间。它相当于NewTimer(d).C。在计时器触发之前,垃圾收集器不会恢复底层计时器。如果效率有问题,使用NewTimer代替,并调用Timer。如果不再需要计时器,请停止。
func After(d Duration) <-chan Time
该函数返回一个通道:chan,存储的是d时间间隔之后的当前时间。相当于:return NewTimer(d).C
加入go友会,我们一起进步吧!
邀请码:Gopher-12538-1769