一. Goroutine池
- 本质是一个生产者消费者模型。Linux多线程——生产者消费者模型_linux多线程生产者与消费者-CSDN博客
- 可以有效控制goroutine数量,防止暴涨,只创建一定数量的协程。
下面来一个例子:需求随机生成数字,计算一个数字的各个位数之和。例如数字123,结果为1 + 2 + 3 = 6。
package main
import (
"fmt"
"math/rand"
)
type Jober struct {
Id int
RandNum int
}
type Resulter struct {
Job *Jober
//结果
Sum int
}
func createPool(num int, job <-chan *Jober, res chan<- *Resulter) {
for i := 0; i < num; i++ {
//创建num个goroutine
go func(jobchan <-chan *Jober, reschan chan<- *Resulter) {
for j := range jobchan { //有数据才会进去
//任务 计算数每个位之和
num := j.RandNum
sum := 0
for num != 0 {
i := num % 10
sum += i
num /= 10
}
//结果放入管道
reschan <- &Resulter{
Job: j,
Sum: sum,
}
}
}(job, res)
}
}
func main() {
job_ch := make(chan *Jober, 128) //保存任务,即要求的随机数
res_ch := make(chan *Resulter, 128) //保存结果
createPool(64, job_ch, res_ch)
//创建打印协程
go func(res_ch <-chan *Resulter) {
for data := range res_ch { //有结果才会进去
fmt.Printf("id : %d, randnum : %d, resSum : %d\n", data.Job.Id, data.Job.RandNum, data.Sum)
}
}(res_ch)
//主协程生成随机数
id := 0
for {
id++
tmp := &Jober{
Id: id,
RandNum: rand.Int(),
}
//随机数放入管道
job_ch <- tmp
}
}
二. 定时器
定时器使用的是Go语言内置的time包。Go语言标准库文档中文版
- 时间到了,执行只执行一次
package main
import (
"fmt"
"time"
)
func main() {
//1. timer 基本使用
//NewTimer创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间。
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(2 * time.Second)
<-timer2.C
fmt.Println("时间到1")
//报错,死锁
//fatal error: all goroutines are asleep - deadlock!
// <-timer2.C
// fmt.Println("时间到2")
//3. timer实现延时功能
//(1)
time.Sleep(time.Second) //延时1秒
//(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("timer4 已经执行")
}()
b := timer4.Stop()
if b {
fmt.Println("timer4已经关闭")
}
//5. 重置定时器
timer5 := time.NewTimer(2 * time.Second)
timer5.Reset(time.Second)
fmt.Println(time.Now())
fmt.Println(<-timer5.C)
}
- Ticker:时间到了,多次执行。
三. select
3.1 select多路复用
在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。传统方式可以用下面的代码实现:
func main() {
c1 := make(chan int)
c2 := make(chan int)
for {
fmt.Println("begin...")
data, ok := <-c1
//...
data, ok = <-c2
//...
fmt.Println("end...")
}
}
这种方式虽然可以实现从多个通道接受值的需求,但是运行性能差。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。
select的使用类似Switch语句,它有一系列case分支和默认分支。每一个case对应一个通道的通信(发生和接收)过程。select会一直等待,直到某个case的通信完成时,就会执行case分支对应的语句。格式如下:
select {
case <-chan1:
//如果chan1成功读到数据,则进行该case处理语句
case chan2<-:
//如果成功向chan2写入数据,则进行该case处理语句
default:
//如果上面都没成功,则进行default处理流程
}
- select可以同时监听一个或多个channel,直到其中一个channel ready
- 如果同时多个channel ready,则随机执行一个
- 可以用于判断管道是否存满