标签导航:

go并发编程:sync.mutex失效及解决方案

本文分析一个Go并发编程案例,演示了sync.Mutex失效导致并发计数结果不准确的原因,并提供两种解决方案。 案例目标是使用1000个协程对一个变量进行累加,预期结果为1000,但实际结果却往往是随机值。

Go并发编程中sync.Mutex失效了:为什么我的并发计数结果不准确?

问题代码:

原代码错误地将sync.Mutex的声明放在for循环内部,导致每个协程都创建了一个独立的锁,无法保证对共享变量的原子操作。

package main

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

func haslockandwait() {
    var a = 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            var locker sync.Mutex // 错误:每个协程都有独立的锁
            locker.Lock()
            a++
            locker.Unlock()
        }(i)
    }
    wg.Wait()
    fmt.Println("最终结果:", a)
}

解决方案一:全局锁

将sync.Mutex的声明移到for循环外部,使其成为全局锁,确保所有协程使用同一个锁来保护共享变量。

package main

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

func hasLockAndWait() {
    var a = 0
    var wg sync.WaitGroup
    var locker sync.Mutex // 正确:全局锁

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            locker.Lock()
            a++
            locker.Unlock()
        }(i)
    }
    wg.Wait()
    fmt.Println("最终结果:", a)
}

解决方案二:使用atomic.AddInt64

使用sync/atomic包中的AddInt64函数进行原子操作,无需显式加锁,更高效。 这避免了锁的开销,尤其在高并发场景下更具优势。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func atomicAdd() {
    var a int64 = 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&a, 1)
        }()
    }
    wg.Wait()
    fmt.Println("最终结果:", a)
}

通过以上两种方法,可以有效解决sync.Mutex失效的问题,确保并发计数结果的准确性。 选择哪种方法取决于具体场景和性能要求,atomic.AddInt64通常在高并发场景下更优。