Go中的通道

目录

1.学习内容

1.channel通道

2.关闭通道和通道上的范围循环

3.Goroutine池

4.定向通道

5.time包中的通道相关函数

6.select语句

7.Go语言中的CSP模型

2.总结


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中大部分的重要知识,这些知识是底层中需要具备的能力,所以需要我们进行磨练,对于所有的知识进行回顾,这样对于我们在后面的学习中可能会有更好的帮助,所以要注意其中的重要问题。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值