Go 學習筆記10.併發編程例子 定時器、打點器、工作池、速率限制、原子計數器

定時器

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)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章