使用引用同时处理结构片

Gus*_*tav 1 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)
}
Run Code Online (Sandbox Code Playgroud)

Gus*_*yer 24

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

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

通过返回新切片:

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

room.Windows = addWindow(room.Windows)
Run Code Online (Sandbox Code Playgroud)

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

func addWindow(room *Room) {
    room.Windows = append(room.Windows, Window{1, 1})
}
Run Code Online (Sandbox Code Playgroud)

对于第二个问题,您必须确保不会以不安全的方式同时更改值.有很多方法可以解决它:

使用频道

您可以通过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)
}
Run Code Online (Sandbox Code Playgroud)

并且addWindow反而会类似于:

func createWindow(windows chan Window) {
    windows <- Window{1, 1}
}
Run Code Online (Sandbox Code Playgroud)

这种方式创建是并发的,但房间的实际操作不是.

添加互斥字段

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

type Room struct {
    m       sync.Mutex
    Windows []Window
}
Run Code Online (Sandbox Code Playgroud)

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

room.m.Lock()
room.Windows = append(room.Windows, window)
room.m.Unlock()
Run Code Online (Sandbox Code Playgroud)

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

如果您在独占(受保护)区域中存在易发生恐慌的逻辑,那么将Unlock呼叫推迟到该区域之后可能是个好主意Lock.许多人只是简单地将一个接一个地放在一起,即使是在简单的操作中,也只是因此他们不必确定这样做是否安全.如果你不确定,这可能是一个好主意.

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

添加全局互斥锁

在更加不寻常的情况下,这很可能不适用于您尝试处理的情况,但很好地了解,您可以选择保护逻辑本身而不是保护数据.一种方法是使用全局互斥变量,其中包含以下内容:

var addWindowMutex sync.Mutex

func addWindow(room *Room) {
    addWindowMutex.Lock()
    room.Windows = append(room.Windows, Window{1, 1})
    addWindowMutex.Unlock()
}
Run Code Online (Sandbox Code Playgroud)

addWindow无论是谁调用它,这种方式本身都受到保护.这种方法的优点是你不依赖于空间的实现来做到这一点.缺点是,无论并行处理多少房间,只有一个goroutine会进入专属区域(与先前的解决方案不同).

在执行此操作时,请记住,还应保护读取 room.Windows或在独占区域中正在变异的任何数据,以防仍然存在并发性同时更改它.

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