深入理解Go语言中的并发编程【27】【Goroutine的使用、定时器、select】


Goroutine池

worker pool(goroutine池)

  • 本质上是生产者消费者模型
  • 可以有效控制goroutine数量,防止暴涨
  • 需求:
    • 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6
    • 随机生成数字进行计算
  • 控制台输出结果如下:

null

package main

import (
    "fmt"
    "math/rand"
)

type Job struct {
    
    
    // id
    Id int
    // 需要计算的随机数
    RandNum int
}

type Result struct {
    
    
    // 这里必须传对象实例
    job *Job
    // 求和
    sum int
}

func main() {
    
    
    // 需要2个管道
    // 1.job管道
    jobChan := make(chan *Job, 128)
    // 2.结果管道
    resultChan := make(chan *Result, 128)
    // 3.创建工作池
    createPool(64, jobChan, resultChan)
    // 4.开个打印的协程
    go func(resultChan chan *Result) {
    
    
        // 遍历结果管道打印
        for result := range resultChan {
    
    
            fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,
                result.job.RandNum, result.sum)
        }
    }(resultChan)
    var id int
    // 循环创建job,输入到管道
    for {
    
    
        id++
        // 生成随机数
        r_num := rand.Int()
        job := &Job{
    
    
            Id:      id,
            RandNum: r_num,
        }
        jobChan <- job
    }
}

// 创建工作池
// 参数1:开几个协程
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
                }
                // 想要的结果是Result
                r := &Result{
    
    
                    job: job,
                    sum: sum,
                }
                //运算结果扔到管道
                resultChan <- r
            }
        }(jobChan, resultChan)
    }
}

定时器

定时器

  • Timer:时间到了,执行只执行1次

  • 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,以避免从该通道的读取不正确的成功。

package main

import (
    "fmt"
    "time"
)

func main() {
    
    
    // 1.timer基本使用
    //timer1 := time.NewTimer(2 * time.Second)
    //t1 := time.Now()
    //fmt.Printf("t1:%v\n", t1)
    //t2 := <-timer1.C
    //fmt.Printf("t2:%v\n", t2)

    // 2.验证timer只能响应1次
    //timer2 := time.NewTimer(time.Second)
    //for {
    
    
    // <-timer2.C
    // fmt.Println("时间到")
    //}

    // 3.timer实现延时的功能
    //(1)
    //time.Sleep(time.Second)
    //(2)
    //timer3 := time.NewTimer(2 * time.Second)
    //<-timer3.C
    //fmt.Println("2秒到")
    //(3)
    //<-time.After(2*time.Second)
    //fmt.Println("2秒到")

    // 4.停止定时器
    //timer4 := time.NewTimer(2 * time.Second)
    //go func() {
    
    
    // <-timer4.C
    // fmt.Println("定时器执行了")
    //}()
    //b := timer4.Stop()
    //if b {
    
    
    // fmt.Println("timer4已经关闭")
    //}

    // 5.重置定时器
    timer5 := time.NewTimer(3 * time.Second)
    timer5.Reset(1 * time.Second)
    fmt.Println(time.Now())
    fmt.Println(<-timer5.C)

    for {
    
    
    }
}
  • Ticker:时间到了,多次执行

  • type Ticker

    type Ticker struct {
        C <-chan Time // 周期性传递时间信息的通道
        // 内含隐藏或非导出字段
    }
    

    Ticker保管一个通道,并每隔一段时间向其传递"tick"。


func NewTicker

func NewTicker(d Duration) *Ticker

NewTicker返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。如果d<=0会panic。关闭该Ticker可以释放相关资源。

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。


func Sleep

func Sleep(d Duration)

Sleep阻塞当前go程至少d代表的时间段。d<=0时,Sleep会立刻返回。

Example


func After

func After(d Duration) <-chan Time

After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C。

Example


func Tick

func Tick(d Duration) <-chan Time

Tick是NewTicker的封装,只提供对Ticker的通道的访问。如果不需要关闭Ticker,本函数就很方便。

Example

package main

import (
    "fmt"
    "time"
)

func main() {
    
    
    // 1.获取ticker对象
    ticker := time.NewTicker(1 * time.Second)
    i := 0
    // 子协程
    go func() {
    
    
        for {
    
    
            //<-ticker.C
            i++
            fmt.Println(<-ticker.C)
            if i == 5 {
    
    
                //停止
                ticker.Stop()
            }
        }
    }()
    for {
    
    
    }
}

select

select多路复用

在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现:

for{
    
    
    // 尝试从ch1接收值
    data, ok := <-ch1
    // 尝试从ch2接收值
    data, ok := <-ch2
    …
}

这种方式虽然可以实现从多个通道接收值的需求,但是运行性能会差很多。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。

select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:

    select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    default:
       // 如果上面都没有成功,则进入default处理流程
    }
  • select可以同时监听一个或多个channel,直到其中一个channel ready
package main

import (
   "fmt"
   "time"
)

func test1(ch chan string) {
    
    
   time.Sleep(time.Second * 5)
   ch <- "test1"
}
func test2(ch chan string) {
    
    
   time.Sleep(time.Second * 2)
   ch <- "test2"
}

func main() {
    
    
   // 2个管道
   output1 := make(chan string)
   output2 := make(chan string)
   // 跑2个子协程,写数据
   go test1(output1)
   go test2(output2)
   // 用select监控
   select {
    
    
   case s1 := <-output1:
      fmt.Println("s1=", s1)
   case s2 := <-output2:
      fmt.Println("s2=", s2)
   }
}
  • 如果多个channel同时ready,则随机选择一个执行
package main

import (
   "fmt"
)

func main() {
    
    
   // 创建2个管道
   int_chan := make(chan int, 1)
   string_chan := make(chan string, 1)
   go func() {
    
    
      //time.Sleep(2 * time.Second)
      int_chan <- 1
   }()
   go func() {
    
    
      string_chan <- "hello"
   }()
   select {
    
    
   case value := <-int_chan:
      fmt.Println("int:", value)
   case value := <-string_chan:
      fmt.Println("string:", value)
   }
   fmt.Println("main结束")
}
  • 可以用于判断管道是否存满
package main

import (
   "fmt"
   "time"
)

// 判断管道有没有存满
func main() {
    
    
   // 创建管道
   output1 := make(chan string, 10)
   // 子协程写数据
   go write(output1)
   // 取数据
   for s := range output1 {
    
    
      fmt.Println("res:", s)
      time.Sleep(time.Second)
   }
}

func write(ch chan string) {
    
    
   for {
    
    
      select {
    
    
      // 写数据
      case ch <- "hello":
         fmt.Println("write hello")
      default:
         fmt.Println("channel full")
      }
      time.Sleep(time.Millisecond * 500)
   }
}

猜你喜欢

转载自blog.csdn.net/m0_52896752/article/details/129797184