文章目录
- Goroutine池
- worker pool(goroutine池)
- 定时器
- 定时器
- select
- select多路复用
Goroutine池
worker pool(goroutine池)
- 本质上是生产者消费者模型
- 可以有效控制goroutine数量,防止暴涨
- 需求:
- 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6
- 随机生成数字进行计算
- 控制台输出结果如下:
package mainimport ("fmt""math/rand"
)type Job struct {// idId 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 intfor r_num != 0 {tmp := r_num % 10sum += tmpr_num /= 10}// 想要的结果是Resultr := &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 mainimport ("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 mainimport ("fmt""time"
)func main() {// 1.获取ticker对象ticker := time.NewTicker(1 * time.Second)i := 0// 子协程go func() {for {//<-ticker.Ci++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 mainimport ("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 mainimport ("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 mainimport ("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)}
}