go语言打怪通关之 ⌈并发编程⌋

定时器

我们常常需要在后面一个时刻运行 Go 代码,或者在某段时间间隔内重复运行。Go 的内置 定时器打点器 特性让这些很容易实现。

package main

import "time"
import "fmt"

func main() {

    // 定时器表示在未来某一时刻的独立事件。你告诉定时器
    // 需要等待的时间,然后它将提供一个用于通知的通道。
    // 这里的定时器将等待 2 秒。
    timer1 := time.NewTimer(time.Second * 2)

    // `<-timer1.C` 直到这个定时器的通道 `C` 明确的发送了
    // 定时器失效的值之前,将一直阻塞。
    <-timer1.C
    fmt.Println("Timer 1 expired")

    // 如果你需要的仅仅是单纯的等待,你需要使用 `time.Sleep`。
    // 定时器是有用原因之一就是你可以在定时器失效之前,取消这个
    // 定时器。这是一个例子
    timer2 := time.NewTimer(time.Second)
    go func() {
        <-timer2.C
        fmt.Println("Timer 2 expired")
    }()
    stop2 := timer2.Stop()
    if stop2 {
        fmt.Println("Timer 2 stopped")
    }
}

打点器

定时器 是当你想要在未来某一刻执行一次时使用的,而 打点器 则是当你想要在固定的时间间隔重复执行准备的。这里是一个打点器的例子,它将定时的执行,直到我们将它停止。

package main

import "time"
import "fmt"

func main() {

    // 打点器和定时器的机制有点相似:一个通道用来发送数据。
    // 这里我们在这个通道上使用内置的 `range` 来迭代值每隔
    // 500ms 发送一次的值。
    ticker := time.NewTicker(time.Millisecond * 500)
    go func() {
        for t := range ticker.C {
            fmt.Println("Tick at", t)
        }
    }()

    // 打点器可以和定时器一样被停止。一旦一个打点停止了,
    // 将不能再从它的通道中接收到值。我们将在运行后 1500ms
    // 停止这个打点器。
    time.Sleep(time.Millisecond * 1500)
    ticker.Stop()
    fmt.Println("Ticker stopped")
}

工作池

在这个例子中,我们将看到如何使用 Go 协程和通道实现一个 工作池

package main

import "fmt"
import "time"

// 这是我们将要在多个并发实例中支持的任务了。这些执行者
// 将从 `jobs` 通道接收任务,并且通过 `results` 发送对应
// 的结果。我们将让每个任务间隔 1s 来模仿一个耗时的任务。
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "processing job", j)
        time.Sleep(time.Second)
        results <- j * 2
    }
}

func main() {

    // 为了使用 worker 工作池并且收集他们的结果,我们需要
    // 2 个通道。
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // 这里启动了 3 个 worker,初始是阻塞的,因为
    // 还没有传递任务。
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // 这里我们发送 9 个 `jobs`,然后 `close` 这些通道
    // 来表示这些就是所有的任务了。

    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)

    // 最后,我们收集所有这些任务的返回值。
    for a := 1; a <= 9; a++ {
        <-results
    }
}

速率限制

速率限制 是一个重要的控制服务资源利用和质量的途径。Go 通过 Go 协程、通道和 打点器 优美的支持了速率限制。

package main

import "time"
import "fmt"

func main() {

    // 首先我们将看一下基本的速率限制。假设我们想限制我们
    // 接收请求的处理,我们将这些请求发送给一个相同的通道。
    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)

    // 这个 `limiter` 通道将每 200ms 接收一个值。这个是
    // 速率限制任务中的管理器。
    limiter := time.Tick(time.Millisecond * 200)

    // 通过在每次请求前阻塞 `limiter` 通道的一个接收,我们限制
    // 自己每 200ms 执行一次请求。
    for req := range requests {
        <-limiter
        fmt.Println("request", req, time.Now())
    }

    // 有时候我们想临时进行速率限制,并且不影响整体的速率控制
    // 我们可以通过 通道缓冲 来实现。
    // 这个 `burstyLimiter` 通道用来进行 3 次临时的脉冲型速率限制。
    burstyLimiter := make(chan time.Time, 3)

    // 想将通道填充需要临时改变次的值,做好准备。
    for i := 0; i < 3; i++ {
        burstyLimiter <- time.Now()
    }

    // 每 200 ms 我们将添加一个新的值到 `burstyLimiter`中,
    // 直到达到 3 个的限制。
    go func() {
        for t := range time.Tick(time.Millisecond * 200) {
            burstyLimiter <- t
        }
    }()

    // 现在模拟超过 5 个的接入请求。它们中刚开始的 3 个将
    // 由于受 `burstyLimiter` 的“脉冲”影响。
    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }
    close(burstyRequests)
    for req := range burstyRequests {
        <-burstyLimiter
        fmt.Println("request", req, time.Now())
    }
}

原子计数器

Go 中最主要的状态管理方式是通过通道间的沟通来完成的,我们在 工作池 的例子中碰到过,但是还是有一些其他的方法来管理状态的。这里我们将看看如何使用 sync/atomic 包在多个 Go 协程中进行 原子计数

package main

import "fmt"
import "time"
import "sync/atomic"
import "runtime"

func main() {

    // 我们将使用一个无符号整形数来表示(永远是正整数)这个计数器。
    var ops uint64 = 0

    // 为了模拟并发更新,我们启动 50 个 Go 协程,对计数
    // 器每隔 1ms 进行一次加一操作。
    for i := 0; i < 50; i++ {
        go func() {
            for {
                // 使用 `AddUint64` 来让计数器自动增加,使用
                // `&` 语法来给出 `ops` 的内存地址。
                atomic.AddUint64(&ops, 1)

                // 允许其它 Go 协程的执行
                runtime.Gosched()
            }
        }()
    }

    // 等待一秒,让 ops 的自加操作执行一会。
    time.Sleep(time.Second)

    // 为了在计数器还在被其它 Go 协程更新时,安全的使用它,
    // 我们通过 `LoadUint64` 将当前值拷贝提取到 `opsFinal`
    // 中。和上面一样,我们需要给这个函数所取值的内存地址 `&ops`
    opsFinal := atomic.LoadUint64(&ops)
    fmt.Println("ops:", opsFinal)
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海岸星的清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值