目录
1.学习内容
今天我们来复习一下go语言中通道中的重要知识。
1.channel通道
通道类似于管道,从一端到一端,利用通道进行通信。
1.什么是通道
通道就是goroutine之间的通道,可以让之间进行通信。
每个通道都有与之相关的类型
package channal
import "fmt"
func Demo01() {
var a chan int//进行申明
fmt.Printf("%T, %v", a, a)
if a == nil {
a = make(chan int)//如果通道为空的话,我们就需要进行make
fmt.Println(a)
}
test(a)
}
func test(a chan int) {
//传的是地址
fmt.Printf("%T, %v", a, a)
}
package channal
import "fmt"
func Demo02() {
var ch1 chan bool
ch1 = make(chan bool)
go func() {
for i := 0; i < 10; i++ {
fmt.Println("子goroutine的值", i)
}
//循环结束后,进行写数据
fmt.Println("结束")
ch1 <- true
}()
data := <-ch1
fmt.Println("main...data", data)
fmt.Println("主函数退出")
}
2.通道的使用方法
package channal
import (
"fmt"
)
func Demo03() {
ch1 := make(chan int)
go func() {
fmt.Println("子执行")
data := <-ch1
fmt.Println(data)
}()
//time.Sleep(5 * time.Millisecond)
ch1 <- 10
fmt.Println("over")
}
注意不能发生死锁问题,有读操作就需要写操作,这样才可以使程序完成.
2.关闭通道和通道上的范围循环
下面写一段关于关闭通道的代码
package channal
import (
"fmt"
"time"
)
func Demo04() {
//关闭通道
ch1 := make(chan int)
ch2 := make(chan int)
go senedData(ch1, ch2)
go func() {
for {
//time.Sleep(time.Second)
v, ok := <-ch1
if !ok {
fmt.Println(ok)
break
}
fmt.Println(v)
}
}()
//利用for range进行打印没有关闭的通道,这样王我们就不需要进行判断返回了
go func() {
for v := range ch2 {
//time.Sleep(time.Second)
fmt.Println(v)
}
fmt.Println("over")
}()
time.Sleep(time.Second * 3)
}
func senedData(ch1, ch2 chan int) {
for i := 0; i < 10; i++ {
ch1 <- i //写入到通道
ch2 <- 1
}
close(ch1) //通道进行关闭
}
接下来我们可以了解一下带有缓冲的通道,这样可以把数据先存在缓冲区
package channal
import "fmt"
func Demo05() {
//非缓存的通道
//没有缓冲区的通道,那就是需要一边读数据另一边就要写数据
ch1 := make(chan int)
fmt.Println(len(ch1), cap(ch1)) //len()代表了现在缓冲区里面含有多少数据
//带缓存的通道
//这样就可以进行存储数据,当满了就会出现阻塞问题
ch2 := make(chan int, 10)
//带有缓冲区是有队列的性质进行读取数据的
fmt.Println(len(ch2), cap(ch2))
ch2 <- 100
fmt.Println(len(ch2), cap(ch2))
ch3 := make(chan int, 5)
go SenedData(ch3)
for v := range ch3 {
//这样就可以看到数据的储存问题
fmt.Println(v)
}
}
func SenedData(ch3 chan int) {
for i := 0; i < 10; i++ {
ch3 <- i //写入到通道
}
close(ch3) //通道进行关闭
}
3.Goroutine池
这个是go学习中文文档里面的知识,很有意义
package Concurrent
import (
"fmt"
"math/rand"
)
type Job struct {
//id
Id int
//需要计算的随机数
RandNum int
}
type Result struct {
//这里传对象的实例
job *Job
//求和
sum int
}
func Demo05() {
//建立两个管道
//job管道
jobchan := make(chan *Job, 128)
//结果管道
resultchan := make(chan *Result, 128)
//创建工作池
createPool(64, jobchan, resultchan)
//开打印的协程
go func(resultchan chan *Result) {
//遍历结果管道进行打印
for result := range resultchan {
fmt.Println(result.job.Id, result.job.RandNum, result.sum)
}
}(resultchan)
var id int
//循环创建job,输入到管道
for i := 0; i < 20; i++ {
id++
//生成随机数
r_num := rand.Int() //随机返回一个非负的随机数
job := &Job{
Id: id,
RandNum: r_num,
}
jobchan <- job
}
}
// 创建工作池
// 开多个线程
func createPool(num int, jobchan chan *Job, resultchan chan *Result) {
for i := 0; i < num; i++ {
go func(jobchan chan *Job, resultchan chan *Result) {
//执行运算
//遍历job管道所有数据,进行相加
for job := range jobchan {
//随机数接过来
r_num := job.RandNum
//随机数每一位相加
//定义返回值
var sum int
for r_num != 0 {
tmp := r_num % 10
sum += tmp
r_num /= 10
} //想要的结果是
r := &Result{
job: job,
sum: sum,
}
//结果放到管道
resultchan <- r
}
}(jobchan, resultchan)
}
}
4.定向通道
通道有两种,双向通道是即可以接受数据,也可以进行写入数据,
但是单向通道就是只能执行其中的一个操作,接下来就要编译一段带向通道的代码,主要需要区分两种代码的书写的不同形式。说一可以理解到这些问题。
单向通道主要是用于进行约束
但是大部分时间我们都是用双向通道,但是有时候需进行限制的时候,就可以对代码进行限制
package channal
import "fmt"
func Demo07() {
//chan<- T,只支持写数据
//<-chan T,只支持读数据
ch1 := make(chan int) //可以进行读写
ch2 := make(chan<- int) //只能进行写
ch3 := make(<-chan int) //只能进行读数据
go fun1(ch2)
fmt.Println(ch1)
go fun2(ch3)
}
// 设计函数
func fun1(ch chan<- int) {
ch <- 100
fmt.Println("fun1函数结束")
}
func fun2(ch <-chan int) {
<-ch
fmt.Println("fun2函数结束")
}
5.time包中的通道相关函数
type Timer
type Timer struct {
C <-chan Time
// 内含隐藏或非导出字段
}
Timer类型代表单次时间事件。当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的。
func NewTimer
func NewTimer(d Duration) *Timer
NewTimer创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间。
func AfterFunc
func AfterFunc(d Duration, f func()) *Timer
AfterFunc另起一个go程等待时间段d过去,然后调用f。它返回一个Timer,可以通过调用其Stop方法来取消等待和对f的调用。
func (*Timer) Reset
func (t *Timer) Reset(d Duration) bool
Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。
func (*Timer) Stop
func (t *Timer) Stop() bool
Stop停止Timer的执行。如果停止了t会返回真;如果t已经被停止或者过期了会返回假。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。
主要是需要超时器Timer,接下来就是要看看其中的代码
这个就要就是来控制一个程序的具体时长,为了我们对程序的更高的可控性
package channal
import (
"fmt"
"time"
)
func Demo08() {
//创建一个计时器,d时间后触发
timer := time.NewTimer(3 * time.Second)
fmt.Printf("%T\n", timer)
fmt.Println(time.Now()) //2023-06-01 16:51:17.075714 +0800 CST m=+0.002299501
//此处等待,会发生阻塞
ch1 := timer.C
fmt.Println(<-ch1) //2023-06-01 16:51:20.0827602 +0800 CST m=+3.009345701
timer2 := time.NewTimer(5 * time.Second)
//开始goroutine,来处理出发后的时间
go func() {
<-timer2.C
fmt.Println("结束")
}()
time.Sleep(3 * time.Second)
flag := timer2.Stop()
if flag {
//这里本来是需要进行五秒中的,但是在三秒后就停止了
fmt.Println("timer2停止")
}
}
这里我们就是展示这两个函数,它们的具体作用会在select中体现,接下来就让我们看看其中的用处。
package channal
import (
"fmt"
"time"
)
func Demo09() {
//func After(d Duration)
//返回的是一个通道:chan,存储的是d时间间隔之后的当前时间
/*func After(d Duration) <-chan Time {
return NewTimer(d).C
}*/
ch := time.After(3 * time.Second)
fmt.Printf("%T\n", ch)
fmt.Println(time.Now()) //2023-06-01 17:01:58.121873 +0800 CST m=+0.003057501
fmt.Println(<-ch) //2023-06-01 17:02:01.1327682 +0800 CST m=+3.013952701
}
6.select语句
select语句和switch语句很是相似,也有case和default
package channal
import (
"fmt"
"time"
)
func Demo10() {
//使用select语句
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(3 * time.Second)
ch1 <- 100
}()
go func() {
time.Sleep(3 * time.Second)
ch2 <- 200
}()
//如果增加了defaul,那么这里就是需要休眠,要不然mian线程会结束
time.Sleep(3 * time.Second)
//注意在两者选择的时候,是随机挑选
select {
case num1 := <-ch1:
fmt.Println(num1)
case num2 := <-ch2:
fmt.Println(num2)
default:
fmt.Println("没有可选择的")
}
}
package channal
import (
"fmt"
"time"
)
func Demo11() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(3 * time.Second)
ch1 <- 100
}()
go func() {
time.Sleep(3 * time.Second)
ch2 <- 200
}()
select {
case <-ch1:
fmt.Println("case1可以执行")
case <-ch2:
fmt.Println("case1可以执行")
case <-time.After(3 * time.Second):
fmt.Println("case3执行")
}
}
selec主要就是鉴定通道的数据流动,上面就是主要的代码,在很多的时候会为我们提供很多的遍历
7.Go语言中的CSP模型
这里大家可以看看千锋教育的讲解,这里对其讲解进行搬抄。
我们也可以对GMP的底层原理进行了解,这样对于我们认识整个并发模型极其原理会有很大的帮助。
2.总结
以上就是包含了channel中大部分的重要知识,这些知识是底层中需要具备的能力,所以需要我们进行磨练,对于所有的知识进行回顾,这样对于我们在后面的学习中可能会有更好的帮助,所以要注意其中的重要问题。