In-depth understanding of concurrent programming in Go language [27] [Goroutine use, timer, select]


Goroutine pool

worker pool(goroutine池)

  • Essentially a producer consumer model
  • Can effectively control the number of goroutines to prevent skyrocketing
  • need:
    • Calculate the sum of the digits of a number, such as the number 123, the result is 1+2+3=6
    • Generate random numbers for calculations
  • The console output is as follows:

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

timer

  • Timer: When the time is up, the execution is only executed once

  • type Timer

    type Timer struct {
        C <-chan Time
        // 内含隐藏或非导出字段
    }
    

    The Timer type represents a single time event. When the Timer expires, the current time will be sent to C, unless the Timer was created by the AfterFunc function.


func NewTimer

func NewTimer(d Duration) *Timer

NewTimer creates a Timer that expires after at least d elapsed period of time, sending the current time to its own C field.


func AfterFunc

func AfterFunc(d Duration, f func()) *Timer

AfterFunc starts another goroutine to wait for the time period d to pass, and then calls f. It returns a Timer, and the wait and call to f can be canceled by calling its Stop method.

func (*Timer) Reset

func (t *Timer) Reset(d Duration) bool

Reset causes t to restart timing, and (after this method returns) waits for the time period d to elapse before it expires. Returns true if t is pending when called; returns false if t has expired or is stopped.


func (*Timer) Stop

func (t *Timer) Stop() bool

Stop stops the execution of Timer. Returns true if t is stopped; returns false if t has been stopped or expired. Stop does not close channel tC to avoid incorrect success of reads from this channel.

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: Time is up, execute multiple times

  • type Ticker

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

    Ticker holds a channel and passes "ticks" to it at regular intervals.


func NewTicker

func NewTicker(d Duration) *Ticker

NewTicker returns a new Ticker, the Ticker contains a channel field, and will send the current time to the channel every time period d. It adjusts the time interval or discards tick messages to accommodate slow receivers. If d<=0 will panic. Closing the Ticker can release related resources.

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop closes a Ticker. After closing, no more tick messages will be sent. Stop does not close the channel tC to avoid incorrect success of reads from this channel.


func Sleep

func Sleep(d Duration)

Sleep blocks the current goroutine for a period of at least d. When d<=0, Sleep will return immediately.

Example


func After

func After(d Duration) <-chan Time

After will send the current time to the return value after another thread has passed the time period d. Equivalent to NewTimer(d).C.

Example


func Tick

func Tick(d Duration) <-chan Time

Tick ​​is a package of NewTicker, which only provides access to Ticker's channel. This function is convenient if you don't need to close the 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 multiplexing

In some scenarios we need to receive data from multiple channels at the same time. When the channel is receiving data, if there is no data to receive, it will be blocked. You may write the following code to use traversal to achieve:

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

Although this method can meet the requirement of receiving values ​​from multiple channels, the operating performance will be much worse. To cope with this scenario, Go has a built-in select keyword, which can respond to operations of multiple channels at the same time.

The use of select is similar to the switch statement, it has a series of case branches and a default branch. Each case corresponds to the communication (receiving or sending) process of a channel. select will wait until the communication operation of a case is completed, and then execute the statement corresponding to the case branch. The specific format is as follows:

    select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    default:
       // 如果上面都没有成功,则进入default处理流程
    }
  • select can monitor one or more channels at the same time until one of the channels is 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)
   }
}
  • If multiple channels are ready at the same time, randomly select one to execute
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结束")
}
  • Can be used to determine whether the pipeline is full
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)
   }
}

Guess you like

Origin blog.csdn.net/m0_52896752/article/details/129797184