Golang中atomic.LoadInt64打印值顺序不正确的问题

Golang中atomic.LoadInt64打印值顺序不正确的问题 我以某种方式实现了 atomic.LoadInt64,期望它能按顺序打印值。但它打印的是随机值。这里有什么问题? 由于我对原子操作理解不深,我在这里遗漏了什么概念,导致无法正确实现它?

package main

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

var wg sync.WaitGroup

var counter int64

func concurrencyGenerator() {

	gs := 1000

	wg.Add(gs)

	for i := 0; i < gs; i++ {
		go func() {

			atomic.AddInt64(&counter, 1)
			fmt.Println("Value : ", atomic.LoadInt64(&counter))

			wg.Done()
		}()
	}

}

func main() {

	concurrencyGenerator()
	wg.Wait()

	fmt.Println(counter)
}

Go


更多关于Golang中atomic.LoadInt64打印值顺序不正确的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我在之前的解释中遗漏了一些内容:

你两次使用的原子函数:

  • atomic.AddInt64(&counter, 1)
  • atomic.LoadInt64(&counter)

这是两个独立的原子操作,在这两个操作之间可能已经过去了任意长的时间。对 fmt.Println 的调用最终很可能会通过一个操作系统系统调用来将输出写入终端(或文件等),这可能导致 Go 运行时在几微秒后(并且 counter 已经递增了许多次)才在某个操作系统线程上执行该操作。

你可以改为这样做:

fmt.Println("Value : ", atomic.AddInt64(&counter, 1))

但这并不会改变 goroutine 调度器会调度你的 1000 个 goroutine 并以非确定性的顺序执行它们这一事实。

更多关于Golang中atomic.LoadInt64打印值顺序不正确的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我无意冒犯,但在原子操作方面,我不得不问:如果你对原子操作没有深入的理解,为什么要使用它们呢?如果是为了学习原子操作,那就忽略我的问题。但我想确认,你是否遇到了某个问题,并且在尚未理解原子操作应该如何工作的情况下,就认为原子操作是解决方案。不要陷入那个老生常谈的工具定律认知偏差

原子操作本身并不同步。它们只是确保操作完全执行,或者在可能失败的操作中,失败意味着操作从未发生,因此你可以重试而不会导致数据不一致。

如果你希望按顺序访问计数器,那么不要使用并发,直接使用循环即可。并行/并发的全部意义在于让不同的执行过程同时运行(因此任务#1与#2同时运行,所以它们可能以任何顺序完成)。

尽管这是针对C++的,但我强烈推荐Fedor Pikus在CppCon上关于原子操作/无锁/性能的视频:

问题在于你对原子操作的顺序保证存在误解。atomic.LoadInt64 确实能保证读取操作的原子性,但它不保证 goroutine 的执行顺序

在你的代码中,1000个 goroutine 并发执行,虽然每个 AddInt64LoadInt64 操作本身是原子的,但 goroutine 的调度顺序是随机的。这意味着:

  1. 某个 goroutine 可能先增加计数器
  2. 但另一个 goroutine 可能先打印值
  3. 打印的顺序完全取决于 goroutine 的调度时机

如果你需要按顺序打印,需要额外的同步机制。这里是一个使用通道来保证顺序的示例:

package main

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

type orderedResult struct {
	seq   int
	value int64
}

func main() {
	const goroutines = 1000
	var counter int64
	var wg sync.WaitGroup
	wg.Add(goroutines)

	// 通道用于收集结果并按顺序输出
	resultCh := make(chan orderedResult, goroutines)
	orderCh := make(chan struct{}, 1) // 用于控制增加操作的顺序

	// 启动一个 goroutine 专门负责按顺序打印
	go func() {
		results := make([]orderedResult, goroutines)
		for i := 0; i < goroutines; i++ {
			results[i] = <-resultCh
		}
		// 按顺序打印
		for _, r := range results {
			fmt.Printf("Value: %d\n", r.value)
		}
	}()

	for i := 0; i < goroutines; i++ {
		go func(seq int) {
			defer wg.Done()
			
			// 通过通道确保增加操作按顺序执行
			orderCh <- struct{}{}
			newVal := atomic.AddInt64(&counter, 1)
			<-orderCh
			
			// 将结果发送到通道,包含顺序编号
			resultCh <- orderedResult{seq: seq, value: newVal}
		}(i)
	}

	wg.Wait()
	close(resultCh)
	fmt.Printf("Final counter: %d\n", counter)
}

如果你只是需要最终看到所有值(不要求顺序),但希望看到每个 goroutine 读取时的准确值,可以这样修改原代码:

package main

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

func main() {
	const goroutines = 1000
	var counter int64
	var wg sync.WaitGroup
	wg.Add(goroutines)

	// 使用互斥锁保护打印顺序(但执行顺序仍然是随机的)
	var mu sync.Mutex
	for i := 0; i < goroutines; i++ {
		go func(id int) {
			defer wg.Done()
			
			newVal := atomic.AddInt64(&counter, 1)
			
			mu.Lock()
			fmt.Printf("Goroutine %d: Value: %d\n", id, newVal)
			mu.Unlock()
		}(i)
	}

	wg.Wait()
	fmt.Printf("Final counter: %d\n", counter)
}

关键点:

  • atomic 包保证单个操作的原子性,但不保证多个操作的执行顺序
  • goroutine 的调度是非确定性的
  • 如果需要顺序性,需要额外的同步机制(如通道、互斥锁或条件变量)
回到顶部