https://futures.yunsonbai.top/?hmsr=yunsonbai.top
贵金属行情

如何控制Go语言goroutine的数量

https://futures.yunsonbai.top/?hmsr=yunsonbai.top

原文连接

我们在使用Go语言编写并发程序时,其中一个重要的问题是如何控制goroutine的数量。使用过多的goroutine可能会导致系统资源不足,从而导致性能下降甚至系统崩溃。在本文中,我们将讨论几种不同的方法来控制goroutine的数量,以确保我们的程序具有最佳的性能和稳定性。

使用缓冲通道

在Go语言中,可以使用通道来实现goroutine之间的通信,来限制goroutine数量。如果没有设置缓冲区,那么通道默认是阻塞的。这意味着发送者必须等待接收者接收数据,否则发送者将一直阻塞。相反,如果通道被缓冲了,那么发送者将不会被阻塞,除非通道已满(限制的关键)。

因此,我们可以使用缓冲通道来控制goroutine的数量。我们可以创建一个带有固定大小的缓冲通道,并在程序中启动多个goroutine来执行任务。只有当通道中有空闲位置时,才能启动新的goroutine。

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
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
// Do some work
results <- j * 2
fmt.Printf("Worker %d finished job %d\n", id, j)
}
}

func main() {
numJobs := 10
numWorkers := 3

jobs := make(chan int, numJobs)
results := make(chan int, numJobs)

// Launch workers
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}

// Add jobs to the channel
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)

// Collect results from workers
for r := 1; r <= numJobs; r++ {
res := <-results
fmt.Println(res)
}
}

使用sync.WaitGroup

另一个常用的方法是使用sync.WaitGroup。WaitGroup是一个计数信号量,用于等待一组goroutine完成它们的任务。可以通过Add()方法向WaitGroup添加需要等待的goroutine数量。每个goroutine完成任务后,可以通过Done()方法向WaitGroup报告它已完成。

在主程序中,使用Wait()方法等待所有的goroutine完成。这将阻塞主程序的执行,直到所有的goroutine都完成为止。

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
28
package main

import (
"fmt"
"sync"
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
// Do some work here
fmt.Printf("Worker %d finished\n", id)
}

func main() {
numWorkers := 3

var wg sync.WaitGroup

// Launch workers
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, &wg)
}

// Wait for all workers to finish
wg.Wait()
}

使用semaphore

Semaphore是一种非常流行的并发控制机制,它可以用于限制并发访问资源的数量。semaphore维护了一个计数器,用于跟踪当前正在使用资源的数量。当某个goroutine需要访问资源时,它必须先获取semaphore的锁。如果当前有太多的goroutine正在使用资源,那么请求锁的goroutine将被阻塞,直到其他goroutine释放锁。

Go语言中没有原生的semaphore实现,但可以使用channel和goroutine来模拟semaphore。例如,创建一个带有固定缓冲区大小的通道,并启动多个goroutine来执行任务。只有当通道中有空闲位置时,才能启动新的goroutine。这种方式类似于使用缓冲通道来控制goroutine的数量。

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
28
29
30
31
32
33
34
package main

import (
"fmt"
)

func worker(id int, sem chan struct{}) {
fmt.Printf("Worker %d waiting\n", id)
sem <- struct{}{}
fmt.Printf("Worker %d started\n", id)
// Do some work here
fmt.Printf("Worker %d finished\n", id)
<-sem
}

func main() {
numWorkers := 3
maxConcurrent := 2

sem := make(chan struct{}, maxConcurrent)

// Launch workers
for w := 1; w <= numWorkers; w++ {
go worker(w, sem)
}

// Wait for all workers to finish
for i := 0; i < maxConcurrent; i++ {
sem <- struct{}{}
}
for i := 0; i < maxConcurrent; i++ {
<-sem
}
}

小结

Go在高并发上实属优秀,如果放任它的高并发也不见得就能提升性能,我们在构建相关组件时建议要考虑一下如何限制同时运行goroutine的数量,尤其是在我们资源有限的时候,给goroutine加上一定的”加锁“,其实是为了系统更出色的完成任务。

yunsonbai wechat
公众号:技术and生活