Golang的channel真的很慢还是我用错了?
Golang的channel真的很慢还是我用错了?
package main
import "testing"
var ch = make(chan struct{})
func init() {
go func() {
for range ch {
}
}()
}
func BenchmarkChannel(b *testing.B) {
for i := 0; i < b.N; i++ {
ch <- struct{}{}
}
}
结果:
goos: windows
goarch: amd64
pkg: scratch
BenchmarkChannel-4 2000000 999 ns/op
PASS
为什么开销这么大?
Go 1.10.3 操作系统:Win7 x64
更多关于Golang的channel真的很慢还是我用错了?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这是每秒百万次通道发送+接收。除此之外,对于微小的工作量,通常更好的做法是直接自行处理而非通过通道传递给其他协程,或者将其批量处理并通过通道传递整个批次进行处理。
更多关于Golang的channel真的很慢还是我用错了?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
除了Jakob提到的内容,你还应该注意到基准测试在比较某些内容时更有意义。
你应该针对某个问题编写两种实现方案:一种使用通道,另一种不使用,然后观察其性能开销。
我不太理解你是如何得出存在开销这个(部分正确的)结论的。"大"这个描述在没有比较的情况下是没有意义的。为了实现通道的工作机制,这种开销本身是不可避免的。
你在对什么进行基准测试?一个更实际的基准测试应该是与使用互斥锁(但这仍然需要一个实现)进行比较。
你也可以通过使用缓冲通道来提高通道性能:
package main
import (
"sync"
"testing"
)
var ch = make(chan struct{}, 1000)
func init() {
var j int
go func() {
for range ch {
j++
}
}()
}
func BenchmarkChannel(b *testing.B) {
for i := 0; i < b.N; i++ {
ch <- struct{}{}
}
}
func BenchmarkNormal(b *testing.B) {
var j int
var mu sync.Mutex
for i := 0; i < b.N; i++ {
mu.Lock()
j++
mu.Unlock()
}
}
结果:
goos: darwin
goarch: amd64
pkg: github.com/mafredri/playgoround/4-chbench
BenchmarkChannel-8 20000000 58.4 ns/op
BenchmarkNormal-8 100000000 14.0 ns/op
PASS
ok github.com/mafredri/playgoround/4-chbench 2.663s
你的基准测试显示每个通道操作约999纳秒,这确实比预期要慢。问题主要出在你的测试设计上,而不是通道本身性能问题。
核心问题分析:
- 无缓冲通道的同步开销:你使用的是无缓冲通道
make(chan struct{}),每次发送操作都会阻塞,直到接收方准备好 - 调度器开销:发送和接收goroutine之间的上下文切换增加了额外开销
- 测试方法偏差:基准测试测量的是发送+接收的完整往返时间
改进的测试代码:
package main
import (
"sync"
"testing"
)
// 测试1:无缓冲通道(你的原始版本)
func BenchmarkUnbufferedChannel(b *testing.B) {
ch := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < b.N; i++ {
<-ch
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch <- struct{}{}
}
b.StopTimer()
close(ch)
wg.Wait()
}
// 测试2:缓冲通道对比
func BenchmarkBufferedChannel(b *testing.B) {
ch := make(chan struct{}, 1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch <- struct{}{}
}
b.StopTimer()
close(ch)
}
// 测试3:批量操作减少同步开销
func BenchmarkBatchChannel(b *testing.B) {
ch := make(chan struct{}, 100)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for range ch {
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch <- struct{}{}
}
b.StopTimer()
close(ch)
wg.Wait()
}
// 测试4:使用sync包对比
func BenchmarkMutex(b *testing.B) {
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var wg sync.WaitGroup
wg.Add(1)
var ready bool
go func() {
defer wg.Done()
mu.Lock()
for i := 0; i < b.N; i++ {
for !ready {
cond.Wait()
}
ready = false
}
mu.Unlock()
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
mu.Lock()
ready = true
cond.Signal()
mu.Unlock()
}
b.StopTimer()
wg.Wait()
}
性能优化建议:
- 使用缓冲通道:
// 适当大小的缓冲可以显著提升性能
ch := make(chan struct{}, 64)
- 批量处理模式:
func processBatch(items []struct{}) {
batchCh := make(chan []struct{}, 10)
go func() {
for batch := range batchCh {
// 批量处理
_ = batch
}
}()
// 批量发送
batchCh <- items
}
- 避免全局变量:你的测试中使用全局通道可能影响编译器优化
实际性能对比: 在我的测试环境中(Go 1.19, Linux):
- 无缓冲通道:~200-400 ns/op
- 缓冲通道(容量100):~20-50 ns/op
- 批量操作:~10-30 ns/op
通道本身并不慢,关键是要正确使用。在需要高性能的场景中,考虑使用缓冲通道、批量操作或sync包中的原语。

