
本文旨在解释在 Golang 并发编程中,为何使用缓冲通道(buffered channel)有时反而比非缓冲通道(unbuffered channel)更慢。我们将通过示例代码分析,深入探讨缓冲通道的初始化开销以及它对程序性能的影响,并提供优化建议。
在 Golang 中,通道(channel)是 goroutine 之间进行通信和同步的重要机制。通道分为缓冲通道和非缓冲通道两种类型。直观上,缓冲通道似乎应该比非缓冲通道更高效,因为它允许发送者在通道未满时继续发送数据,而无需立即等待接收者。然而,在某些情况下,缓冲通道的性能反而不如非缓冲通道。这通常与缓冲通道的初始化开销有关。
让我们通过一个具体的例子来理解这个问题。以下代码片段展示了一个使用缓冲通道和非缓冲通道的 HTML 文本提取程序:
package main
import (
"fmt"
"math/rand"
"os"
"sync"
"time"
sel "code.google.com/p/go-html-transform/css/selector"
h5 "code.google.com/p/go-html-transform/h5"
gnhtml "code.google.com/p/go.net/html"
)
// Find a specific HTML element and return its textual element children.
func main() {
test := `
<html>
<head>
<title>This is the test document!</title>
<style>
header: color=blue;
</style>
</head>
<body>
<div id="h" class="header">This is some text</div>
</body>
</html>`
// Get a parse tree for this HTML
h5tree, err := h5.NewFromString(test)
if err != nil {
die(err)
}
n := h5tree.Top()
// Create a Chain object from a CSS selector statement
chn, err := sel.Selector("#h")
if err != nil {
die(err)
}
// Find the item. Should be a div node with the text "This is some text"
h := chn.Find(n)[0]
// run our little experiment this many times total
var iter int = 100000
// When buffering, how large shall the buffer be?
var bufSize uint = 100
// Keep a running total of the number of times we've tried buffered
// and unbuffered channels.
var bufCount int = 0
var unbufCount int = 0
// Keep a running total of the number of nanoseconds that have gone by.
var bufSum int64 = 0
var unbufSum int64 = 0
// Call the function {iter} times, randomly choosing whether to use a
// buffered or unbuffered channel.
for i := 0; i < iter; i++ {
if rand.Float32() < 0.5 {
// No buffering
unbufCount += 1
startTime := time.Now()
getAllText(h, 0)
unbufSum += time.Since(startTime).Nanoseconds()
} else {
// Use buffering
bufCount += 1
startTime := time.Now()
getAllText(h, bufSize)
bufSum += time.Since(startTime).Nanoseconds()
}
}
unbufAvg := unbufSum / int64(unbufCount)
bufAvg := bufSum / int64(bufCount)
fmt.Printf("Unbuffered average time (ns): %v\n", unbufAvg)
fmt.Printf("Buffered average time (ns): %v\n", bufAvg)
}
// Kill the program and report the error
func die(err error) {
fmt.Printf("Terminating: %v\n", err.Error())
os.Exit(1)
}
// Walk through all of a nodes children and construct a string consisting
// of c.Data where c.Type == TextNode
func getAllText(n *gnhtml.Node, bufSize uint) string {
var texts chan string
if bufSize == 0 {
// unbuffered, synchronous
texts = make(chan string)
} else {
// buffered, asynchronous
texts = make(chan string, bufSize)
}
wg := sync.WaitGroup{}
// Go walk through all n's child nodes, sending only textual data
// over the texts channel.
wg.Add(1)
nTree := h5.NewTree(n)
go func() {
nTree.Walk(func(c *gnhtml.Node) {
if c.Type == gnhtml.TextNode {
texts <- c.Data
}
})
close(texts)
wg.Done()
}()
// As text data comes in over the texts channel, build up finalString
wg.Add(1)
finalString := ""
go func() {
for t := range texts {
finalString += t
}
wg.Done()
}()
// Return finalString once both of the goroutines have finished.
wg.Wait()
return finalString
}在这个例子中,getAllText 函数使用 goroutine 和 channel 来提取 HTML 节点中的文本。它接受一个 bufSize 参数,用于指定通道的缓冲区大小。如果 bufSize 为 0,则使用非缓冲通道;否则,使用具有指定缓冲区大小的缓冲通道。
立即学习“go语言免费学习笔记(深入)”;
最初的实验结果表明,使用缓冲区大小为 100 的缓冲通道的平均运行时间明显高于非缓冲通道。这似乎违反了直觉。
原因分析
关键在于缓冲通道的初始化开销。当创建一个缓冲通道时,Go 运行时需要分配一块内存来存储通道中的元素。缓冲区越大,分配的内存就越多。在上述例子中,每次调用 getAllText 函数时,都会创建一个新的缓冲通道。如果缓冲区大小设置得过大,频繁的内存分配和回收可能会导致性能下降。
优化方案
为了验证这个假设,我们将缓冲区大小从 100 减小到 10。再次运行程序,得到的结果如下:
Buffered average time (ns): 21930 Buffered average time (ns): 22721 Buffered average time (ns): 23011 Buffered average time (ns): 23707 Buffered average time (ns): 27701 Buffered average time (ns): 28325 Buffered average time (ns): 28851 Buffered average time (ns): 29641 Buffered average time (ns): 30417 Buffered average time (ns): 32600 Unbuffered average time (ns): 21077 Unbuffered average time (ns): 21490 Unbuffered average time (ns): 22332 Unbuffered average time (ns): 22584 Unbuffered average time (ns): 26438 Unbuffered average time (ns): 26824 Unbuffered average time (ns): 27322 Unbuffered average time (ns): 27926 Unbuffered average time (ns): 27985 Unbuffered average time (ns): 30322
可以看到,使用缓冲区大小为 10 的缓冲通道的平均运行时间与非缓冲通道的平均运行时间非常接近。这表明,减小缓冲区大小可以有效地降低初始化开销,从而提高程序性能。
总结与建议
此外,还可以考虑以下优化策略:
通过深入理解缓冲通道的特性和潜在的性能问题,我们可以编写出更高效、更可靠的 Golang 并发程序。
以上就是Golang并发:缓冲通道为何有时比非缓冲通道慢?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号