使用引用同时处理结构片

时间:2013-08-27 13:53:42

标签: 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)
}

2 个答案:

答案 0 :(得分:23)

您的逻辑中存在两个不同的问题:第一个问题是切片本身是如何被操纵的,第二个问题是实际的并发问题。

对于切片操作,只需将值按片段作为参数传递将意味着您将无法以切片必须生长或支持时调用网站将看到它的方式改变切片重新分配数组以容纳您要追加的新数据。有两种常见的方法可以解决这个问题。

返回新切片:

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 goroutines生成窗口,并将结果报告给非生动的控制点。例如,您可能有:

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或者在独占区域中正在变异的任何数据也应受到保护,以防仍然存在并发性同时进行更改。< / p>

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

答案 1 :(得分:0)

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}]}