定時器
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)
}