定时器
go 语言自身实现了定时器
package main
import "time"
import "fmt"
func main() {
// 创建一个定时器对象 , 指定需要等待的时间,然后它将提供一个用于通知的通道
timer1 := time.NewTimer(time.Second * 1)
// 定时器失效的值之前,将一直阻塞。
<-timer1.C
fmt.Println("Timer 1 expired")
// 如果你需要的仅仅是单纯的等待,你需要使用 `time.Sleep`。
// Stop() 取消这个定时器
timer2 := time.NewTimer(time.Second * 3)
go func() {
<-timer2.C
fmt.Println("Timer 3 expired")
}()
time.Sleep(time.Second * 1)
// 如果这个定时器已经失效,则返回false,如果是我们停止的,则返回true
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 3 stopped")
}
}
打点器
也是go语言的实现的
package main
import "time"
import "fmt"
func main() {
// 500ms 发送一次
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
// Tick at 2020-04-18 21:28:15.499081153 +0800 CST m=+0.500233782
// Tick at 2020-04-18 21:28:15.999139818 +0800 CST m=+1.000292431
}
}()
time.Sleep(time.Millisecond * 1500)
// 打点器也可以停止
ticker.Stop()
fmt.Println("Ticker stopped")
}
工作池
协程+管道 实现工作池
package main
import "fmt"
import "time"
// 参数 id 此处为worker的id号,int类型
// jobs 管道, 只允许取出,取出数据为int类型
// results 管道 只能写出,写入数据为int类型
func worker(id int, jobs <-chan int, results chan<- int) {
// 遍历管道 取出的一种方式
for j := range jobs {
// 打印worker的id号, 并输出管道的取出的值
fmt.Println("worker", id, "processing job", j)
// 睡眠1s,假装在工作
time.Sleep(time.Second)
// 将值x2 写入到结果管道中
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// 启动3个 worker 协程, 他们都是阻塞的,因为jobs还没有被写入数据
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 向jobs 连续写入9个数据
for j := 1; j <= 9; j++ {
jobs <- j
}
// 关闭jobs,jobs 不允许被写入了
close(jobs)
// 取出结果,不存,只是阻塞,等待9次全部执行完
for a := 1; a <= 9; a++ {
<-results
}
}
执行结果,可以看到时3个3个同时执行的
worker 1 processing job 1
worker 2 processing job 2
worker 3 processing job 3
worker 1 processing job 4
worker 2 processing job 5
worker 3 processing job 6
worker 1 processing job 7
worker 2 processing job 8
worker 3 processing job 9
速率限制
速率限制 是重要的控制服务资源和质量的途径
协程 + 通道 + 打点器 实现
package main
import "time"
import "fmt"
func main() {
// 创建requests管道,直接写入5次数据
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
// 创建打点器, 200ms
limiter := time.Tick(time.Millisecond * 200)
// 使用打点器的特性进行阻塞,感觉这个和sleep差不多呢
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}
// -------------------------------------------------------------
// 例子2,最终效果是打印5条,快速连续打印3次,后面200ms打印一次
// 创建缓冲管道
burstyLimiter := make(chan time.Time, 3)
// 将管道填满
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
}
// 协程执行,200ms 写入向管道写入一次
go func() {
for t := range time.Tick(time.Millisecond * 200) {
burstyLimiter <- t
}
}()
// 创建管道,模拟请求,连续请求5次
burstyRequests := make(chan int, 5)
for i := 1; i <= 5; i++ {
burstyRequests <- i
}
close(burstyRequests)
// 接收请求,有5次
for req := range burstyRequests {
// 因limiter里面是满的,迅速取出3个,执行3次,后面会阻塞,定时器200ms一次
<-burstyLimiter
fmt.Println("request", req, time.Now())
}
}
原子计数器
使用
sync/atomic
包在多个 Go 协程中进行 原子计数atomic包的使用 https://www.jianshu.com/p/228c119a7d0e
package main
import "fmt"
import "time"
import "sync/atomic"
import "runtime"
func main() {
// 服务号整数, 永远正数
var ops uint64 = 0
// 50个协程,进行原子性的累加1操作
for i := 0; i < 50; i++ {
go func() {
for {
// 原子增1
atomic.AddUint64(&ops, 1)
// 让出cpu时间片, go语言内置的函数
runtime.Gosched()
}
}()
}
// 等待一秒,让50个协程工作一会
time.Sleep(time.Second)
// 如果一个写操作未完成,有一个读操作就已经发生了,这样读操作使很糟糕的
// 使用atomic.LoadUint64 安全的读取
opsFinal := atomic.LoadUint64(&ops)
fmt.Println("ops:", opsFinal)
}