简体   繁体   English

Golang并发:如何从不同的goroutine附加到相同的切片

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

I have concurrent goroutines which want to append a (pointer to a) struct to the same slice. 我有并发的goroutine想要将(指向a)指针结构附加到同一个切片。 How do you write that in Go to make it concurrency-safe? 你如何在Go中编写它以使其兼容并发?

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. 我想你需要使用go渠道来实现并发安全。 Can anyone contribute with an example? 任何人都可以贡献一个例子吗?

There is nothing wrong with guarding the MySlice = append(MySlice, &OneOfMyStructs) with a sync.Mutex. 使用sync.Mutex保护MySlice = append(MySlice, &OneOfMyStructs)没有任何问题。 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. 但是当然你可以有一个缓冲区大小为len(params)的结果通道所有goroutines发送他们的答案,一旦你的工作完成,你从这个结果通道收集。

If your params has a fixed size: 如果你的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)
}

As all goroutines write to different memory this isn't racy. 由于所有goroutines写入不同的记忆,这不是很有趣。

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. @jimt发布的答案不太正确,因为它错过了在频道中发送的最后一个值,并且最后一个defer wg.Done()从未被调用过。 The snippet below has the corrections. 下面的代码段有更正。

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

package main

import "fmt"
import "sync"

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 < 100; i++ {
        go func(i int) {
            // defer wg.Done()  <- will result in the last int to be missed in the receiving channel
            queue <- T(i)
        }(i)
    }

    go func() {
        // defer wg.Done() <- 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)
}

A channel is the best way to tackle this. 渠道是解决这个问题的最佳方式。 Here is an example which can be run on go playground . 这是一个可以在游乐场上运行的例子。

package main

import "fmt"
import "sync"
import "runtime"

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 < 100; i++ {
        go func(i int) {
            defer wg.Done()

            // Do stuff.
            runtime.Gosched()

            queue <- T(i)
        }(i)
    }

    // Poll the queue for data and append it to the slice.
    // Since this happens synchronously and in the same
    // goroutine/thread, this can be considered safe.
    go func() {
        defer wg.Done()
        for t := range queue {
            slice = append(slice, t)
        }
    }()

    // Wait for everything to finish.
    wg.Wait()

    fmt.Println(slice)
}

Note : The runtime.Gosched() call is there because those goroutines do not yield to the scheduler. 注意runtime.Gosched()调用是存在的,因为那些goroutine不会产生调度程序。 Which would cause a deadlock if we do not explicitly do something to trigger said scheduler. 如果我们没有明确地做某事来触发所述调度程序,那么会导致死锁。 Another option could have been to perform some I/O (eg: print to stdout). 另一种选择可能是执行一些I / O(例如:print to stdout)。 But I find a runtime.Gosched() to be easier and clearer in its intent. 但我发现runtime.Gosched()更容易,更清晰。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM