1

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

2 回答 2

29

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

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

通过返回新切片:

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或正在发生变异的任何数据也应受到保护,以防同时仍有并发更改它。

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

于 2013-08-27T15:07:46.440 回答
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}]}
于 2013-08-27T14:21:13.983 回答