【问题标题】:Working with slices of structs concurrently using references使用引用同时处理结构切片
【发布时间】:2013-08-27 13:53:42
【问题描述】:

我有一个 JSON,需要对其进行一些处理。它使用了一个切片,我需要以某种方式引用该切片,以便在函数结束时修改 Room-struct。如何以引用类型的方式同时使用此结构?

http://play.golang.org/p/wRhd1sDqtb

type Window struct {
    Height int64 `json:"Height"`
    Width  int64 `json:"Width"`
}
type Room struct {
    Windows []Window `json:"Windows"`
}

func main() {
    js := []byte(`{"Windows":[{"Height":10,"Width":20},{"Height":10,"Width":20}]}`)
    fmt.Printf("Should have 2 windows: %v\n", string(js))
    var room Room
    _ = json.Unmarshal(js, &room)

    var wg sync.WaitGroup
    // Add many windows to room
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            addWindow(room.Windows)
        }()
    }
    wg.Wait()

    js, _ = json.Marshal(room)
    fmt.Printf("Sould have 12 windows: %v\n", string(js))
}

func addWindow(windows []Window) {
    window := Window{1, 1}
    // Do some expensive calculations
    fmt.Printf("Adding %v to %v\n", window, windows)
    windows = append(windows, window)
}

【问题讨论】:

  • 在您的情况下,我认为对Window 结构使用sync.Mutex,允许您在添加窗口时锁定/解锁它,应该可以解决问题。当然,你可以做一种渠道类型的解决方案,但这不一定会更好。 play.golang.org/p/0dFSHQf9rX

标签: go


【解决方案1】:

您的逻辑中有两个不同的问题:第一个是切片本身的操作方式,第二个涉及实际的并发问题。

对于切片操作,简单地将切片作为参数传递值将意味着您将无法以调用站点在切片必须增长或支持时看到它的方式改变切片重新分配数组以容纳您要附加的新数据。有两种常见的处理方法。

通过返回新切片:

func addWindow(windows []Window) []Window {
    return append(windows, Window{1, 1})
}

room.Windows = addWindow(room.Windows)

或者通过提供调用站点维护引用的可变参数:

func addWindow(room *Room) {
    room.Windows = append(room.Windows, Window{1, 1})
}

对于第二个问题,您必须确保值不会以不安全的方式同时发生变异。也有很多方法可以解决它:

使用频道

您可以要求 N 个 goroutine 生成窗口,而不是直接操纵房间,并将它们的结果报告回一个非 racy 控制点。例如,您可能有:

windows := make(chan Window, N)
for i := 0; i < N; i++ { 
    go createWindow(windows)
}
for i := 0; i < N; i++ {
    room.Windows = append(room.Windows, <-windows)
}

addWindow 看起来类似于:

func createWindow(windows chan Window) {
    windows <- Window{1, 1}
}

这样创建是并发的,但房间的实际操作不是。

添加互斥字段

在类型本身中有一个私有的互斥字段也很典型,例如:

type Room struct {
    m       sync.Mutex
    Windows []Window
}

然后,每当操作并发敏感字段时,使用互斥锁保护独占区域:

room.m.Lock()
room.Windows = append(room.Windows, window)
room.m.Unlock()

理想情况下,此类互斥锁的使用应保持封装在靠近类型本身的位置,因此很容易发现它是如何使用的。因此,您会经常看到在类型本身的方法中使用互斥锁(例如room.addWindow)。

如果您在排他(受保护)区域中有容易发生恐慌的逻辑,最好将Unlock 调用推迟到Lock 调用之后。很多人只是简单地把一个接一个,即使是在简单的操作中,这样他们就不必考虑这样做是否安全。如果您不确定,这可能是个好主意。

非常重要:在大多数情况下,按值复制具有互斥字段的结构是个坏主意。相反,使用指向原始值的指针。原因是互斥体内部依赖于其字段的地址不会改变原子操作才能正常工作。

添加全局互斥锁

在更不寻常的情况下,很可能不适用于您要处理的案例,但很清楚,您可以选择保护逻辑本身而不是保护数据。一种方法是使用全局互斥变量,其中包含以下内容:

var addWindowMutex sync.Mutex

func addWindow(room *Room) {
    addWindowMutex.Lock()
    room.Windows = append(room.Windows, Window{1, 1})
    addWindowMutex.Unlock()
}

这种方式addWindow 本身受到保护,无论是谁调用它。这种方法的优点是您不依赖空间的实现来完成它。一个缺点是只有一个 goroutine 会进入独占区域,无论并行处理多少个房间(之前的解决方案不是这样)。

在执行此操作时,请记住读取 room.Windows 或任何在独占区域中发生变异的数据也应受到保护,以防同时发生并发更改。

最后,就像一些自发的反馈一样,请检查那些错误值。忽略错误是一种非常糟糕的做法,无论它只是一个示例还是严肃的代码。很多时候,即使在构建这样的示例代码时,您也会发现错误。

【讨论】:

  • 来自谷歌搜索并发和数据结构。我希望所有答案都像这个一样。谢谢!
【解决方案2】:
package main

import (
        "encoding/json"
        "fmt"
        "sync"
)

type Window struct {
        Height int64 `json:"Height"`
        Width  int64 `json:"Width"`
}
type Room struct {
        mu      sync.Mutex
        Windows []Window `json:"Windows"`
}

func main() {
        js := []byte(`{"Windows":[{"Height":10,"Width":20},{"Height":10,"Width":20}]}`)
        fmt.Printf("Should have 2 windows: %v\n", string(js))
        var room Room
        _ = json.Unmarshal(js, &room)

        var wg sync.WaitGroup
        // Add meny windows to room
        for i := 0; i < 10; i++ {
                wg.Add(1)
                go func() {
                        defer wg.Done()
                        addWindow(&room)
                }()
        }
        wg.Wait()

        js, _ = json.Marshal(room)
        fmt.Printf("Sould have 12 windows: %v\n", string(js))
}

func addWindow(r *Room) {
        window := Window{1, 1}
        fmt.Printf("Adding %v to %v\n", window, r.Windows)

        r.mu.Lock()
        defer r.mu.Unlock()
        r.Windows = append(r.Windows, window)

}

Should have 2 windows: {"Windows":[{"Height":10,"Width":20},{"Height":10,"Width":20}]}
Adding {1 1} to [{10 20} {10 20}]
Adding {1 1} to [{10 20} {10 20} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1}]
Adding {1 1} to [{10 20} {10 20} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1} {1 1}]
Sould have 12 windows: {"Windows":[{"Height":10,"Width":20},{"Height":10,"Width":20},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1},{"Height":1,"Width":1}]}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-18
    • 1970-01-01
    • 2019-09-15
    • 2013-09-13
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 2015-02-21
    相关资源
    最近更新 更多