You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中如何并发过滤追加切片并保序且无数据竞争?

Go并发过滤切片:保证顺序且无数据丢失的实现

问题背景

以下代码尝试并发过滤输入切片中的偶数,但存在数据竞争导致数据丢失、结果顺序混乱的问题:

input := []int{1, 2, 3, 4, 5, 6, 7, 8}

wg := sync.WaitGroup{}

var result []int

for i, num := range input {
    wg.Add(1)
    go func(num, i int) {
        if num%2 == 0 {
            result = append(result, num)
        }

        wg.Done()
    }(num, i)
}
wg.Wait()

fmt.Println(result)

需求是得到有序结果:2,4,6,8,且输入是动态无上限的,无法预知过滤后的数据量。


解决方案

方法一:基于索引的临时切片(推荐,无锁高效)

利用输入的固定顺序,预先创建与输入等长的临时切片(用指针标记有效值),每个goroutine仅写入对应索引的位置,最后再收集有效结果:

package main

import (
	"fmt"
	"sync"
)

func main() {
	input := []int{1, 2, 3, 4, 5, 6, 7, 8}

	wg := sync.WaitGroup{}
	// 创建与输入长度一致的临时切片,用*int表示是否有有效值
	temp := make([]*int, len(input))

	for i, num := range input {
		wg.Add(1)
		// 传递当前循环的num和索引i
		go func(val, idx int) {
			defer wg.Done()
			if val%2 == 0 {
				// 每个索引仅由一个goroutine写入,无数据竞争
				temp[idx] = &val
			}
		}(num, i)
	}
	wg.Wait()

	// 遍历临时切片,收集有效结果
	var result []int
	for _, ptr := range temp {
		if ptr != nil {
			result = append(result, *ptr)
		}
	}

	fmt.Println(result) // 输出:[2 4 6 8]
}

优势:无锁操作,完全保证顺序,性能最优,适合大部分场景。

方法二:带索引的通道接收(适合超大输入)

若输入量极大,不想预先分配大内存,可通过通道传递带原始索引的结果,主线程按索引顺序整理:

package main

import (
	"fmt"
	"sync"
)

func main() {
	input := []int{1, 2, 3, 4, 5, 6, 7, 8}

	wg := sync.WaitGroup{}
	// 通道缓冲设为输入长度,避免goroutine阻塞
	ch := make(chan struct {
		idx int
		val int
	}, len(input))

	for i, num := range input {
		wg.Add(1)
		go func(val, idx int) {
			defer wg.Done()
			if val%2 == 0 {
				ch <- struct {
					idx int
					val int
				}{idx, val}
			}
		}(num, i)
	}

	// 等待所有goroutine完成后关闭通道
	go func() {
		wg.Wait()
		close(ch)
	}()

	// 初始化临时切片存储结果
	temp := make([]*int, len(input))
	for res := range ch {
		temp[res.idx] = &res.val
	}

	// 收集最终有序结果
	var result []int
	for _, ptr := range temp {
		if ptr != nil {
			result = append(result, *ptr)
		}
	}

	fmt.Println(result) // 输出:[2 4 6 8]
}

优势:无需一次性分配与输入等长的内存,适合超大批量数据处理,仍能保证顺序。

方法三:互斥锁保护切片(不推荐)

用sync.Mutex保护append操作可解决数据丢失,但无法保证顺序,且会让并发操作退化为串行,丧失并发优势,仅作参考:

package main

import (
	"fmt"
	"sync"
)

func main() {
	input := []int{1, 2, 3, 4, 5, 6, 7, 8}

	wg := sync.WaitGroup{}
	var mu sync.Mutex
	var result []int

	for i, num := range input {
		wg.Add(1)
		go func(val, idx int) {
			defer wg.Done()
			if val%2 == 0 {
				mu.Lock()
				result = append(result, val)
				mu.Unlock()
			}
		}(num, i)
	}
	wg.Wait()

	// 结果顺序无法保证,例如可能输出[4 2 6 8]
	fmt.Println(result)
}

内容的提问来源于stack exchange,提问作者yssachan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 18:45:36