Golang中使用原子操作时缓存一致性会导致某些线程无限循环吗?

Golang中使用原子操作时缓存一致性会导致某些线程无限循环吗? 当我使用以下测试代码时,某些线程会陷入循环。但如果我们添加了 println()os.Create()os.Open(),这种情况就不会发生。

	var casValue = new(int64)
	*casValue = 0
	var old int64 = 0
	var new int64 = 1
	var container *list.List
	container = list.New()
	var wg sync.WaitGroup
	wg.Add(10)
	t1 := time.Now()
	for i := 0; i < 10; i++ {
		go func(i int) {
			for j := 0; j < 10000; {
				if atomic.CompareAndSwapInt64(casValue, old, new) {
					var test = "hello world,hello world,hello world"
					container.PushBack(test)
					*casValue = 0
					j++
				}
				//println("hello")、os.Create("test")、os.Open("test") will solve this problem
			}
			println("casValue=", *casValue)
			wg.Done()
		}(i)
	}
	lentcy := time.Since(t1)
	wg.Wait()
	fmt.Println(container.Len())

更多关于Golang中使用原子操作时缓存一致性会导致某些线程无限循环吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

当我取消 println(“hello”) 的注释时,没有数据冲突。原因是什么?

更多关于Golang中使用原子操作时缓存一致性会导致某些线程无限循环吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go版本从1.13.5升级到1.14.1后,问题已解决,非常感谢!

这是一个典型的缓存一致性问题导致的线程饥饿现象。在Go语言中,原子操作本身是线程安全的,但内存可见性需要依赖正确的同步机制。

问题出现在以下代码段:

if atomic.CompareAndSwapInt64(casValue, old, new) {
    var test = "hello world,hello world,hello world"
    container.PushBack(test)
    *casValue = 0  // 这里直接赋值,不是原子操作
    j++
}

主要问题:

  1. 非原子写操作*casValue = 0 不是原子操作,多个goroutine可能同时看到不一致的值
  2. 缺少内存屏障:直接赋值不会创建内存屏障,其他CPU核心可能看不到最新的值
  3. container非线程安全list.List不是并发安全的,多个goroutine同时调用PushBack()会导致数据竞争

修复方案:

package main

import (
    "container/list"
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

func main() {
    var casValue int64 = 0
    var container = list.New()
    var mu sync.Mutex
    var wg sync.WaitGroup
    
    wg.Add(10)
    t1 := time.Now()
    
    for i := 0; i < 10; i++ {
        go func(i int) {
            for j := 0; j < 10000; {
                // 使用原子操作检查和设置
                if atomic.CompareAndSwapInt64(&casValue, 0, 1) {
                    mu.Lock()
                    var test = "hello world,hello world,hello world"
                    container.PushBack(test)
                    mu.Unlock()
                    
                    // 使用原子操作重置
                    atomic.StoreInt64(&casValue, 0)
                    j++
                }
            }
            wg.Done()
        }(i)
    }
    
    wg.Wait()
    fmt.Println("container length:", container.Len())
    fmt.Println("time elapsed:", time.Since(t1))
}

或者使用更简洁的sync/atomic方案:

func main() {
    var casValue int64 = 0
    var counter int64 = 0
    var wg sync.WaitGroup
    
    wg.Add(10)
    
    for i := 0; i < 10; i++ {
        go func() {
            for j := 0; j < 10000; {
                if atomic.CompareAndSwapInt64(&casValue, 0, 1) {
                    // 执行需要同步的操作
                    atomic.AddInt64(&counter, 1)
                    
                    // 使用原子存储确保可见性
                    atomic.StoreInt64(&casValue, 0)
                    j++
                }
            }
            wg.Done()
        }()
    }
    
    wg.Wait()
    fmt.Println("total operations:", atomic.LoadInt64(&counter))
}

关于你提到的println()os.Create()等调用能解决问题,这是因为:

  1. 这些系统调用会隐式创建内存屏障
  2. 它们会触发goroutine调度,让其他goroutine有机会执行
  3. 但这不是正确的解决方案,只是掩盖了问题

正确的做法是:

  1. 对共享变量的所有访问都使用原子操作
  2. 对非线程安全的数据结构使用互斥锁保护
  3. 使用atomic.Storeatomic.Load确保内存可见性
回到顶部