showliuzp 2025-10-28 21:13 采纳率: 84.3%
浏览 4
已结题

golang 多协程加锁赋值变量不可预测


 
import (
    "fmt"
    "sync"
)
 
func main() {
    var i int
    var wg sync.WaitGroup
    var mu sync.Mutex
 
    wg.Add(2)
 
//协程1
    go func() {
        defer wg.Done()
        mu.Lock()
        i += 10
        mu.Unlock()
    }()
 
  //协程2
    go func() {
        defer wg.Done()
        mu.Lock()
        i = 10
        mu.Unlock()
    }()
 
 
    wg.Wait()
    fmt.Printf("最终结果:%d\n", i) 
}
 
我在测试两个协程加锁一个全局变量的影响,现在测试了50万次,输出都是20,我的理解:协程1获得了锁,i值为10,释放锁,协程2再执行,赋值10,输出为10,协程2赋值为10,释放锁,协程1获得锁,i+=10,i为20,但始终打印的值都是20,难道每次都是协程2优先获取的调度机会?
 
  • 写回答

3条回答 默认 最新

  • 宝码香车 优质创作者: JavaScript技术领域 2025-10-28 21:38
    关注

    让【宝码香车】来帮您解答,本回答参考DeepSeek编写,并整理提供。

    如果答案让您满意,请采纳、点赞、关注,非常感谢!
    .
    每日一句正能量,让它带给您好心情:真正的快乐,源于内心的满足和对生活的热爱。
    .

    根据你的代码和描述,我来分析一下为什么你测试50万次都输出20。

    问题分析

    你的理解基本正确,但需要补充一些关键点:

    func main() {
        var i int
        var wg sync.WaitGroup
        var mu sync.Mutex
    
    
        wg.Add(2)
    
    
        // 协程1
        go func() {
            defer wg.Done()
            mu.Lock()
            i += 10  // 读取i的值,然后加10
            mu.Unlock()
        }()
    
    
        // 协程2  
        go func() {
            defer wg.Done()
            mu.Lock()
            i = 10  // 直接赋值为10
            mu.Unlock()
        }()
    
    
        wg.Wait()
        fmt.Printf("最终结果:%d\n", i) 
    }
    

    可能的结果

    理论上这个程序可能有两种结果

    1. 结果20:协程1先执行
    • 协程1获取锁:i += 10i = 10
    • 协程2获取锁:i = 10i = 10
    • 最终结果:10
    1. 结果20:协程2先执行
    • 协程2获取锁:i = 10i = 10
    • 协程1获取锁:i += 10i = 20
    • 最终结果:20

    为什么你总是看到20

    你测试50万次都输出20,这确实说明在你的测试环境中,协程2几乎总是先获得执行机会。这可能是由于:

    1. Goroutine调度特性:Go的调度器在某些情况下会有一定的执行顺序偏好
    2. 创建顺序:后创建的goroutine在某些情况下可能更容易先被调度
    3. 系统状态:CPU核心数、系统负载等因素可能影响调度顺序

    验证方法

    要看到不同的结果,可以尝试以下方法:

    方法1:添加随机延迟

    go func() {
        defer wg.Done()
        time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
        mu.Lock()
        i += 10
        mu.Unlock()
    }()
    
    
    go func() {
        defer wg.Done()  
        time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
        mu.Lock()
        i = 10
        mu.Unlock()
    }()
    

    方法2:增加更多goroutine

    func main() {
        for j := 0; j < 100; j++ {
            var i int
            var wg sync.WaitGroup
            var mu sync.Mutex
            
            wg.Add(2)
            
            go func() {
                defer wg.Done()
                mu.Lock()
                i += 10
                mu.Unlock()
            }()
            
            go func() {
                defer wg.Done()
                mu.Lock() 
                i = 10
                mu.Unlock()
            }()
            
            wg.Wait()
            fmt.Printf("测试%d: %d\n", j+1, i)
        }
    }
    

    结论

    你的代码逻辑是正确的,加锁确保了数据竞争的安全性。你总是看到结果20是因为在你的测试环境下,协程2几乎总是先获得执行机会,但这并不是绝对的。在多核CPU上或者不同的系统环境下,你可能会看到不同的结果。

    关键要点

    • 加锁确保了线程安全,这是正确的
    • Goroutine的执行顺序是不确定的
    • 你的测试结果反映了当前环境的调度特性,但不是程序行为的唯一可能
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 11月5日
  • 已采纳回答 10月28日
  • 创建了问题 10月28日