Golang中是否会以某种方式修改goroutines?

Golang中是否会以某种方式修改goroutines? 我曾遇到过一些程序,由于某些全局变量的访问,使用多个goroutine拆分问题反而比使用单个goroutine运行得更慢。当移除这些访问后,程序变得比单goroutine版本快得多,这符合预期。

那么,Go是否会对作为goroutine运行的代码做任何处理……比如在它认为可能存在竞争问题的地方自动添加互斥锁?如果没有,那么如何解释那种情况下的性能下降呢?

谢谢

4 回复

当所有goroutine访问同一个变量时,缓存失效可能会产生影响。

更多关于Golang中是否会以某种方式修改goroutines?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 Telo,

这并不能解释它,因为在示例中,全局变量是一个整数,用于累加计数。goroutine 正在更新这个计数。当我移除那个全局变量的更新后,速度就加快了。

我得尝试重新创建那个具体的示例——那是一个曼德博集合生成器。

Jon

Go 不会自动添加“互斥锁”,因为它无法读取程序员的思维来决定程序应该做什么。

一个可能的解释是垃圾回收。全局变量始终位于堆上,因此每个垃圾回收周期(在 Go 中,这是持续进行的,不会冻结你的程序)都会因为该变量而需要多做一点工作。单个全局变量本身没什么,但如果它有许多指向其他变量的指针字段,垃圾回收器将需要扫描每一个指针。

func main() {
    fmt.Println("hello world")
}

在Go语言中,运行时不会自动为goroutine添加互斥锁或同步原语。性能下降通常是由于共享资源的竞争和调度开销导致的。以下是一个示例,说明全局变量访问如何影响性能:

package main

import (
    "fmt"
    "sync"
    "time"
)

var globalCounter int
var mu sync.Mutex

func withoutMutex() {
    for i := 0; i < 1000000; i++ {
        globalCounter++
    }
}

func withMutex() {
    for i := 0; i < 1000000; i++ {
        mu.Lock()
        globalCounter++
        mu.Unlock()
    }
}

func main() {
    // 测试无锁版本
    start := time.Now()
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            withoutMutex()
        }()
    }
    wg.Wait()
    fmt.Printf("无锁版本耗时: %v, 结果: %d\n", time.Since(start), globalCounter)

    // 重置计数器
    globalCounter = 0

    // 测试有锁版本
    start = time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            withMutex()
        }()
    }
    wg.Wait()
    fmt.Printf("有锁版本耗时: %v, 结果: %d\n", time.Since(start), globalCounter)
}

输出可能类似:

无锁版本耗时: 12.456ms, 结果: 4567892
有锁版本耗时: 345.678ms, 结果: 10000000

性能下降的原因:

  1. 缓存一致性开销:多个goroutine修改同一变量导致CPU缓存频繁失效
  2. 内存争用:对共享内存的并发访问需要串行化
  3. 调度器压力:大量goroutine竞争资源会增加调度延迟

解决方案示例:

// 使用局部变量减少竞争
func optimized() {
    local := 0
    for i := 0; i < 1000000; i++ {
        local++
    }
    mu.Lock()
    globalCounter += local
    mu.Unlock()
}

// 使用通道
func channelSolution() {
    ch := make(chan int, 10)
    for i := 0; i < 10; i++ {
        go func(id int) {
            sum := 0
            for j := 0; j < 1000000; j++ {
                sum++
            }
            ch <- sum
        }(i)
    }
    
    total := 0
    for i := 0; i < 10; i++ {
        total += <-ch
    }
    fmt.Println("通道方案结果:", total)
}

使用go run -race可以检测数据竞争:

go run -race main.go

性能下降的根本原因是共享状态的管理开销,而非Go运行时的自动干预。

回到顶部