小编典典

使用引用同时处理结构片

go

我有一个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)
}

阅读 221

收藏
2020-07-02

共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制作窗户,而不是直接操纵房间,并将其结果报告给非民主控制点。例如,您可能有:

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或在独占区域中进行任何数据更改的任何数据,以防万一仍在进行并发更改。

最后,就像一些无提示的反馈一样,请检查那些错误值。不管是示例还是严肃的代码,忽略错误都是非常糟糕的做法。即使构建这样的示例代码,很多时候您也会捕获错误。

2020-07-02