Go基础:了解 Timers, Tickers, Worker Pools, Waitgroups, Rate Limiting in Go

我们深入探讨了 定时器、计时器、工作池、等待组和速率限制 相关的详细内容。

full lessons here👇:
https://programmerscareer.com/zh-cn/golang-basic-skill/

由AI生成,可能有错误,仅供参考

Topic: Timers

Timers 表示一个将在未来发生的事件。您告诉定时器要等待多长时间,然后它提供一个通道,直到该时间到达通知您。这是如何在 Go 中创建定时器:

1
2
3
timer  := time.NewTimer(2 * time.Second)  
<-timer.C
fmt.Println("Timer fired")

在这个示例中,我们创建了一个将在 2 秒后触发的定时器。<-timer.C 行阻止定时器的 C 通道直到它发送一个值,表示定时器已经触发。

如果您想要在定时器触发前停止它,可以使用 Stop 函数:

1
2
3
4
5
6
7
8
9
timer  := time.NewTimer(2 * time.Second)   
go func() {
<-timer.C
fmt.Println("Timer fired")
}()
stop := timer.Stop()
if stop {
fmt.Println("Timer stopped")
}

在这个示例中,我们在定时器触发前停止了它。

Topic: Tickers

与定时器不同, tickers 不是单次事件,而是用于重复任务的工具。下面是一个创建 tickers 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ticker  := time.NewTicker(500 * time.Millisecond)   
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t, := <-ticker.C:
fmt.Println("Tick at", t)
}
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
done <- true
fmt.Println("Ticker stopped")

在这个示例中,我们创建了一个每 500 毫秒触发一次的 tickers。然后,我们创建了一个匿名 goroutine,监听 ticker.C 通道。在每次 tick 发生时,goroutine 打印 tick 的时间。

Topic: Waitgroups

Go 语言中的 sync 包提供了 WaitGroup。它是为了解决一个非常常见的问题:在有 n 个 goroutines的情况下,我们需要等待这些 goroutines 完成执行。这可以通过 WaitGroup 实现。

您可以认为 wait group 是一个 token 系统或一个记账员,记录任务的执行状态,并在所有任务完成后检查是否所有任务都已经完成。

考虑这个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main  
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(msg string) {
// Schedule the call to WaitGroup's Done to tell we are done.
defer wg.Done()

fmt.Printf("Working on %s\n", msg)
time.Sleep(time.Second)
fmt.Printf("Done working on %s\n", msg)

}
func main() {
// Add a count of the total number of goroutines we're going to use.
wg.Add(2)
go worker("Task 1")
go worker("Task 2")

// Wait until the WaitGroup counter is zero i.e., all routines have finished
wg.Wait()
fmt.Println("All tasks completed!")

}

在这个代码中,main 不会退出直到 worker 完成两个任务。

Topic: Rate Limiting

Rate limiting 是一种控制在给定时间内发送请求的数量的方法。它对于维护服务质量和防止服务器被淹没非常重要。

下面是一个实现 rate limiting 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {  
requests := make(chan int, 5)

for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
limiter := time.Tick(200 * time.Millisecond)
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}

}

在这个示例代码中,我们运行一个循环,发送 5 个请求,并将它们存储在一个通道中。然后,我们设置一个速率限制器,每隔 200 毫秒触发一次。

对于每个请求,我们从 limiter 接收一个 tick,然后打印请求。这确保了我们不处理超过一个请求每 200 毫秒。

Topic: Case Studies: Building a Job Scheduler and a RateLimited Service

Case Study 1: Building a Job Scheduler

软件工程中的一种常见任务是 job scheduling,某些任务需要在特定的时间间隔或指定的将来时间执行。

以下是一个简单的 Go 代码,用于每 2 秒钟执行一次函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func printTime() {
fmt.Println(time.Now())
}

func main() {
tick := time.Tick(2 * time.Second)
go func() {
for range tick {
printTime()
}
}()
// Hold main from exiting
time.Sleep(10 * time.Second)
}

在这个 Go 代码中,我们使用内置的 time.Tick 函数来 scheduling printTime 函数每 2 秒钟执行一次。

Case Study 2: Building a RateLimited Service

基于我们之前讨论的 rate limiter,让我们考虑一个使用该 rate limiter 处理 incoming 请求的 HTTP 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"net/http"
"time"
)

var limiter = time.Tick(200 * time.Millisecond)

func rateLimitedHandler(w http.ResponseWriter, r *http.Request) {
<-limiter // Block until we receive from the limiter channel (i.e., "rate limit" ourselves)
fmt.Fprint(w, "Web page content")
}

func main() {
http.HandleFunc("/", rateLimitedHandler)
http.ListenAndServe(":8080", nil)
}

这将是一个简单的 rateLimited HTTP 服务器概要。

需要注意的是,实际上构建实-world 的 schedulers 和 rateLimited 服务远远超出了这个简单的介绍。但是,如果你拥有这些并发原则和机制的坚实基础,你就可以在 Go 中构建强大的系统。

Topic: Review and Assessment

Review:

我们已经涵盖了许多重要的“Advanced Concurrency”概念:

  1. Timers: 对于想要在将来某个时间执行某事时非常有用。
  2. Tickers: 对于想要在特定的时间间隔中重复执行某事时非常有用。
  3. Worker Pools: 实现 worker pools 可以是一个限制资源使用的好方法。它们用于处理大量 goroutines,执行类似的任务。
  4. Waitgroups: 对于等待一组 goroutines 执行完成时非常有用。
  5. Rate Limiting: 对于确保我们的程序不太过度使用资源或控制某些动作的速率非常重要。

我们使用 channels 和 goroutines 来处理这些并发概念,按照“Go”风格实现了它们。

Assessment:

如果你现在理解这些高级并发概念,那么它是时候进行一个 quiz 了。让我们看看你是否准备好解决复杂的问题,这些技能的应用。

  1. Timer Practice Problem:
    写一个程序,在 3 秒钟后打印“Timer expired”消息。
  2. Tickets Practice Problem:
    写一个函数,每 100ms tick 一次,停止在 1 秒钟后。
  3. Worker Pools Practice Problem:
    创建一个大小为 3 的 worker pool。提交作业来打印数字的平方。该作业将在延迟后打印输入的平方。
  4. Waitgroups Practice Problem:
    写一个程序,使用 WaitGroup 同步两个 goroutines 的执行。
  5. Rate Limiting Problem:
    写一个程序,只允许每秒 3 个请求被处理。

这些问题将给你实践理解这些高级并发概念的机会。如果你能编写出这些代码,恭喜你!如果不能或有疑问,请随时问我,我会帮助你。

记住,学习 Go 或任何编程语言的最好方法是不断实践。继续探索、继续实现。Happy coding! 🦌

English post: https://programmerscareer.com/go-basic-07/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:本文为作者原创,转载请注明出处。

Go基础:了解 File Paths, Directories, Temporary Files and Directories, Embed Directive in Go Go基础:理解错误处理: Errors, Panic, Defer, Recover in Go

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×