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