首页 > 后端开发 > Golang > 正文

Golang并发:缓冲通道为何有时比非缓冲通道慢?

聖光之護
发布: 2025-10-11 08:28:31
原创
730人浏览过

golang并发:缓冲通道为何有时比非缓冲通道慢?

本文旨在解释在 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 函数时,都会创建一个新的缓冲通道。如果缓冲区大小设置得过大,频繁的内存分配和回收可能会导致性能下降。

MindShow
MindShow

MindShow官网 | AI生成PPT,快速演示你的想法

MindShow 1492
查看详情 MindShow

优化方案

为了验证这个假设,我们将缓冲区大小从 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 并发编程中,缓冲通道并非总是比非缓冲通道更高效。
  • 缓冲通道的初始化开销可能会对程序性能产生负面影响,尤其是在频繁创建和销毁通道的情况下。
  • 选择合适的缓冲区大小非常重要。过大的缓冲区可能会导致内存分配和回收的开销增加,而过小的缓冲区则可能导致通道阻塞。
  • 在实际应用中,应根据具体情况选择合适的通道类型和缓冲区大小。如果通道的创建和销毁频率很高,并且传输的数据量较小,则可以考虑使用非缓冲通道或较小的缓冲通道。

此外,还可以考虑以下优化策略:

  1. 重用通道: 避免频繁创建和销毁通道。如果可能,可以重用已有的通道,以减少内存分配和回收的开销。
  2. 使用 sync.Pool: 可以使用 sync.Pool 来管理缓冲通道,从而避免频繁的内存分配。
  3. 分析程序瓶颈: 使用性能分析工具(如 pprof)来识别程序中的性能瓶颈,并针对性地进行优化。

通过深入理解缓冲通道的特性和潜在的性能问题,我们可以编写出更高效、更可靠的 Golang 并发程序。

以上就是Golang并发:缓冲通道为何有时比非缓冲通道慢?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号