Golang并发:如何从不同的goroutine中向同一个切片追加元素

huangapple go评论76阅读模式
英文:

Golang concurrency: how to append to the same slice from different goroutines

问题

我有一些并发的goroutine,它们想要将一个(指向)结构体的指针追加到同一个切片中。在Go语言中,如何编写代码以确保并发安全性?

以下是我使用等待组编写的并发不安全代码示例:

var wg sync.WaitGroup
MySlice := make([]*MyStruct, 0)
for _, param := range params {
    wg.Add(1)
    go func(param string) {
        defer wg.Done()
        OneOfMyStructs := getMyStruct(param)
        MySlice = append(MySlice, &OneOfMyStructs)
    }(param)
}
wg.Wait()

我猜你需要使用Go通道来确保并发安全性。有人可以提供一个示例吗?

英文:

I have concurrent goroutines which want to append a (pointer to a) struct to the same slice.
How do you write that in Go to make it concurrency-safe?

This would be my concurrency-unsafe code, using a wait group:

var wg sync.WaitGroup
MySlice = make([]*MyStruct)
for _, param := range params {
    wg.Add(1)
    go func(param string) {
        defer wg.Done()
        OneOfMyStructs := getMyStruct(param)
        MySlice = append(MySlice, &OneOfMyStructs)
    }(param)
}
wg.Wait()

I guess you would need to use go channels for concurrency-safety. Can anyone contribute with an example?

答案1

得分: 55

使用sync.Mutex来保护MySlice = append(MySlice, &OneOfMyStructs)是没有问题的。但是你也可以使用一个带有缓冲区大小为len(params)的结果通道,让所有的goroutine将它们的答案发送到这个结果通道中,一旦工作完成,你就可以从这个结果通道中收集结果。

如果你的params有固定的大小:

MySlice = make([]*MyStruct, len(params))
for i, param := range params {
    wg.Add(1)
    go func(i int, param string) {
         defer wg.Done()
         OneOfMyStructs := getMyStruct(param)
         MySlice[i] = &OneOfMyStructs
     }(i, param)
}

由于所有的goroutine都写入不同的内存,所以这不会产生竞争条件。

英文:

There is nothing wrong with guarding the MySlice = append(MySlice, &OneOfMyStructs) with a sync.Mutex. But of course you can have a result channel with buffer size len(params) all goroutines send their answers and once your work is finished you collect from this result channel.

If your params has a fixed size:

MySlice = make([]*MyStruct, len(params))
for i, param := range params {
    wg.Add(1)
    go func(i int, param string) {
         defer wg.Done()
         OneOfMyStructs := getMyStruct(param)
         MySlice[i] = &OneOfMyStructs
     }(i, param)
}

As all goroutines write to different memory this isn't racy.

答案2

得分: 27

@jimt发布的答案并不完全正确,因为它遗漏了在通道中发送的最后一个值,并且最后一个defer wg.Done()从未被调用。下面的代码片段进行了修正。

package main

import "fmt"
import "sync"

type T int

func main() {
    var slice []T
    var wg sync.WaitGroup

    queue := make(chan T, 1)

    // 创建数据并将其发送到队列中。
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go func(i int) {
            defer wg.Done() // 将导致最后一个整数在接收通道中丢失
            queue <- T(i)
        }(i)
    }

    go func() {
        // defer wg.Done() <- 由于上面进行了100次`Done()`调用,导致`Wait()`在执行此操作之前继续进行
        for t := range queue {
            slice = append(slice, t)
            wg.Done() // ** 将`Done()`调用移到这里
        }
    }()

    wg.Wait()

    // 现在打印出所有100个整数值
    fmt.Println(slice)
}

请注意,这只是代码的翻译部分,不包括任何其他内容。

英文:

The answer posted by @jimt is not quite right, in that it misses the last value sent in the channel and the last defer wg.Done() is never called. The snippet below has the corrections.

https://play.golang.org/p/7N4sxD-Bai

package main

import &quot;fmt&quot;
import &quot;sync&quot;

type T int

func main() {
    var slice []T
    var wg sync.WaitGroup

    queue := make(chan T, 1)

    // Create our data and send it into the queue.
    wg.Add(100)
    for i := 0; i &lt; 100; i++ {
        go func(i int) {
            // defer wg.Done()  &lt;- will result in the last int to be missed in the receiving channel
            queue &lt;- T(i)
        }(i)
    }

    go func() {
        // defer wg.Done() &lt;- Never gets called since the 100 `Done()` calls are made above, resulting in the `Wait()` to continue on before this is executed
        for t := range queue {
            slice = append(slice, t)
            wg.Done()   // ** move the `Done()` call here
        }
    }()

    wg.Wait()

    // now prints off all 100 int values
    fmt.Println(slice)
}

答案3

得分: 0

我想补充一下,由于你知道你从通道中期望的值的数量,所以你可能不需要使用任何同步原语。只需从通道中读取你期望的数据量,并将其保留即可:

package main

import "fmt"

type T int

func main() {
	var slice []T

	queue := make(chan T)

	// 创建数据并将其发送到队列中。
	for i := 0; i < 100; i++ {
		go func(i int) {
			queue <- T(i)
		}(i)
	}

	for i := 0; i < 100; i++ {
		select {
		case t := <-queue:
			slice = append(slice, t)
		}
	}

	// 现在打印出所有100个整数值
	fmt.Println(slice)
}

select 语句会阻塞,直到通道接收到一些数据,因此我们可以依赖这种行为,在退出之前从通道中读取100次。

在你的情况下,你可以这样做:

package main

func main() {
	MySlice := []*MyStruct{}
	queue := make(chan *MyStruct)

	for _, param := range params {
		go func(param string) {
			OneOfMyStructs := getMyStruct(param)
			queue <- &OneOfMyStructs
		}(param)
	}

	for _ := range params {
		select {
		case OneOfMyStructs := <-queue:
			MySlice = append(MySlice, OneOfMyStructs)
		}
	}
}
英文:

I wanted to add that since you know how many values you are expecting from the channel, you may not need to make use of any synchronization primitives. Just read from the channel as much data as you are expecting and leave it alone:

borrowing @chris' answer

package main

import &quot;fmt&quot;

type T int

func main() {
	var slice []T

	queue := make(chan T)

	// Create our data and send it into the queue.
	for i := 0; i &lt; 100; i++ {
		go func(i int) {
			queue &lt;- T(i)
		}(i)
	}

	for i := 0; i &lt; 100; i++ {
		select {
		case t := &lt;-queue:
			slice = append(slice, t)
		}
	}

	// now prints off all 100 int values
	fmt.Println(slice)
}

The select will block until the channels receives some data, so we can rely on this behaviour to just read from the channel 100 times before exiting.

In your case, you can just do:

package main

func main() {
	MySlice = []*MyStruct{}
	queue := make(chan *MyStruct)

	for _, param := range params {
		go func(param string) {
			OneOfMyStructs := getMyStruct(param)
			queue &lt;- &amp;OneOfMyStructs
		}(param)
	}

	for _ := range params {
		select {
		case OneOfMyStructs := &lt;-queue:
			MySlice = append(MySlice, OneOfMyStructs)
		}
	}
}

huangapple
  • 本文由 发表于 2013年8月29日 06:30:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/18499352.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定