go语言学习笔记24------生产者消费者简易模型①

今天,让我们来探讨下go语言之下的生产者消费者模型,让我们先来了解一下单向channel的定义。

1.channel及应用

默认情况下,通道channel是双向的,也就是,既可以往里面发送数据也可以同里面接收数据。

但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。

单向channel变量的声明非常简单,如下:
var ch1 chan int // ch1是一个正常的channel,是双向的
var ch2 chan<- float64 // ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读int数据

chan<- 表示数据进入管道,要把数据写进管道,对于调用者就是输出。

<-chan 表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入。

可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通 channel:

c := make(chan int, 3)
var send chan<- int = c // send-only
var recv <-chan int = c // receive-only

send <- 1
<-recv

//不能将单向 channel 转换为普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int

示例代码:

package main

import "fmt"

func print(ch chan<- int) {
   for i := 0; i < 5; i++ {
      ch <- i

   }
   close(ch)
}
func recv(ch <-chan int) {
   for num := range ch {
      fmt.Println(num)
   }
}
func main() {
   ch := make(chan int)
   go print(ch)
   recv(ch)
}
//输出结果
//0
//1
//2
//3
//4
//Process finished with exit code 0

2.生产者消费者模型

单向channel最典型的应用是“生产者消费者模型”

所谓“生产者消费者模型”: 某个模块(函数等)负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、协程、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模型。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

package main

import (
   "fmt"
)

type info struct {
   id int
}

func Producer(ch chan<- info) {
   defer close(ch)

   for i := 0; i < 5; i++ {
      commodity := info{id: i + 1}
      ch <- commodity
   }

}
func Handler(ch <-chan info) {

   for num := range ch {
      fmt.Println(num)
   }
}
func main() {
   ch := make(chan info)
   go Producer(ch)
   Handler(ch)
}
//输出结果
//{1}
//{2}
//{3}
//{4}
//{5}
//
//Process finished with exit code 0

3.定时器

3.1time.Timer

Timer是一个定时器。代表未来的一个单一事件,你可以告诉timer你要等待多长时间。

type Timer struct {

C <-chan Time
r runtimeTimer
}

它提供一个channel,在定时时间到达之前,没有数据写入timer.C会一直阻塞。直到定时时间到,系统会自动向timer.C 这个channel中写入当前时间,阻塞即被解除。

定时器的常用操作:

1)实现延迟功能

package main

import (
   "time"
   "fmt"
)

func main() {
<-time.After(time.Second)
fmt.Println("延迟1s打印")
time.Sleep(time.Second)
fmt.Println("休眠1s打印")
time:=time.NewTimer(time.Second)
<-time.C
fmt.Println("延迟1s打印")
}
//输出结果
//延迟1s打印
//休眠1s打印
//延迟1s打印
//
//Process finished with exit code 0

2)定时器停止

package main

import (
   time2 "time"
   "fmt"
)

func main() {

   time:=time2.NewTimer(time2.Second)
   go func() {
      <-time.C
      fmt.Println("定时时间到")
   }()
   time2.Sleep(time2.Second*2)
   time.Stop()
   fmt.Println("计时器停止")
   for{
      ;
   }
}
//输出结果
//定时时间到
//计时器停止

3)定时器重置

package main

import (

   "fmt"
   "time"
)

func main() {
   timer := time.NewTimer(10 * time.Second)
   ok := timer.Reset(1 * time.Second) //重新设置为1s
   fmt.Println("ok = ", ok)
   <-timer.C
   fmt.Println("时间到")

}
//输出结果
//ok =  true
//时间到

用时1s,定时器时间由10s改成1s。

3.2time.Ticker

Ticker是一个周期触发定时的计时器,它会按照一个时间间隔往channel发送系统当前时间,而channel的接收者可以以固定的时间间隔从channel中读取事件。

type Ticker struct {

C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}

示例代码:

package main

import (

   "time"
   "runtime"
   "fmt"
)

func main() {
   timer := time.NewTicker(1 * time.Second)
   i:=0
   go func() {
      for {
         <-timer.C
         fmt.Println(i)
         i++
         if  i==5{

            timer.Stop()
            fmt.Println("定时器关闭")
            runtime.Goexit()
         }
      }
   }()
   for {
      ;
   }
}
//输出结果
//0
//1
//2
//3
//4
//定时器关闭

4.select

select作用

Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。

与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:

select {
case <- chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
default:
    // 如果上面都没有成功,则进入default处理流程
}

在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。

select: 是一个关键字。 监听 channel 上的数据流动(可读、可写)。 语法参考 switch case

1. 每次执行一个 case 分支。

2. 通常将select 放置到循环当中。

3. 每一个case 分支,必须是一个 IO 操作。

4. 如果监听中的case不满足 —— 当前 case 阻塞。

5. 如果监听中的case同时有多个满足, select 选择任意一个来执行。

6. select 语法中的 default 是在所有case不满足情况下,设置的默认处理动作。 通常不设置,防止忙轮询,消耗系统资源。

7.break 只能跳出一个 case 。不能跳出 select 外面的 for

写一个斐波那契数列:

斐波那契数列:前两个数字是1,从第三个数字开始是其前两个数字的和。

如下:
1 1 2 3 5 8 13 21 34 55 89 …

示例代码:

package main

import "fmt"

func Sequence(c chan int, quit chan bool) {
   x := 1
   y := 1
   for {
      select {
      case c <- x:
         x, y = y, x+y
      case <-quit:
         fmt.Println("Quit")
         return
      }
   }

}
func main() {
   ch := make(chan int)
   quit := make(chan bool)
   go func() {
      for i := 0; i < 10; i++ {
         fmt.Println(<-ch)
      }
      quit<-true
   }()
    Sequence(ch,quit)
}

//输出结果
//1
//1
//2
//3
//5
//8
//13
//21
//34
//55
//Quit

超时

有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:

package main

import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int)
   quit := make(chan bool)
   go func() {
      for  {
         select {
         case v:=<-ch:
            fmt.Println(v)
         case <-time.After(time.Second*5):
            fmt.Println("time out")
         quit<-true
         return
         }
      }

   }()
   ch<-888
    <-quit
}

//输出结果
//888
//time out

888输出之后,5s内没有再向ch内写入,5s后系统自动写入时间,实现下面的通道打开执行代码。此项select可以用于服务器超时强踢。

select 超时退出:

使用 select 的一个 case 监听 <-time.After(定时时长)
其他 case 满足, time.After(定时时长) 的定时计时,会被重置!!!
<- time.After(定时时长) 所对应的 case 满足,说明 在计时期间,select 监听的 所有其他 case 都没满足监听条件。
goto 关键字:
    直接跳转到设置的  任意标签位置,继续执行。要求标签不能超出当前 函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值