Go并发编程中锁机制的常见错误:为什么1000个协程累加结果并非1000?

Go并发编程中锁机制的常见错误:为什么1000个协程累加结果并非1000?

Go并发编程中的sync.Mutex锁及常见错误分析

本文剖析一段使用sync.Mutex锁和sync.WaitGroup进行并发编程的Go代码,这段代码试图通过1000个协程累加一个变量,但最终结果与预期(1000)不一致。让我们来分析代码并找出问题所在。

示例代码:

package main  import (     "fmt"     "sync"     "time" )  func main() {     haslockandwait() }  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) }

代码预期结果是a最终值为1000,但实际运行结果往往小于1000。这是因为sync.Mutex的声明位置错误。

问题根源:var locker sync.Mutex这行代码在每个goroutine内部声明,这意味着每个goroutine都创建了一个独立的sync.Mutex实例。这些锁互不干扰,多个goroutine可以同时修改a,导致结果不准确。

解决方案:将sync.Mutex的声明移到for循环之外,使其成为全局锁,确保所有goroutine使用同一个锁来保护a变量。修改后的代码如下:

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函数,它提供原子操作,无需锁即可保证线程安全:

import "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) }

通过以上修改,可以确保并发累加操作的正确性,最终结果将为1000。 这强调了在Go并发编程中正确使用锁机制的重要性,错误的锁使用会导致数据竞争和不正确的程序行为。

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享