2013-08-27 17 views
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) 
} 
+0

在你的情況下,我想爲'Window'結構使用sync.Mutex,允許你在添加窗口的時候鎖定/解鎖它,應該可以做到。當然,你可以做一個渠道類型的解決方案,但這不一定會更好。 http://play.golang.org/p/0dFSHQf9rX – ANisus

回答

19

有你的邏輯兩個不同的問題:第一個是如何切片本身被操縱,而第二個方面的實際併發問題。

對於切片操作,只需將切片按值作爲參數傳遞就意味着您將無法以調用網站在切片生長時或切片支持數組重新分配以適應您要追加的新數據。有兩種常見的方法來處理這個問題。

通過返回新切片:

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夠程生產的窗戶,並有自己的結果報告給非活潑的控制點。例如,你可能有:

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)。

如果您在專屬(受保護)區域中出現恐慌傾向性邏輯,最好在Lock之後推遲Unlock調用。很多人只是簡單地把一個人放在一個接一個的後面,即使是在簡單的操作中,也只是讓他們不必確定是否安全。如果你不確定,這可能是一個好主意。

非常重要:在大多數情況下,按值複製具有互斥量字段的結構是一個壞主意。相反,使用指向原始值的指針。原因在於互斥體在內部依賴於其字段的地址,以防原子操作正常工作。

添加一個全局mutex

更不尋常的情況下,這極有可能不申請你想處理的情況,但是這是很好的瞭解一下,您可以選擇保護邏輯本身而不是保護數據。要做到這一點的方法之一是用一個全局互斥鎖變量,周圍的東西線:

var addWindowMutex sync.Mutex 

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

本身是這種方式保護addWindow,不管是誰打來的。這種方法的優點是你不依賴於房間的實施來做到這一點。缺點是隻有一個goroutine將進入專屬區域,無論並行處理多少個房間(這與先前的解決方案不同)。

在這樣做時,請記住,閱讀room.Windows或任何數據在專用區域被突變也應受到保護,如果仍有併發去上同時更改。

最後,就像一些自發的反饋一樣,檢查這些錯誤值。忽略錯誤是一個非常糟糕的做法,無論它只是一個示例還是嚴重的代碼。很多時候,即使在構建這樣的示例代碼時也會發現錯誤。

+0

很好的反饋,謝謝! – Gustav

+0

來自谷歌搜索併發和數據結構。我希望所有的答案都是這樣的。謝謝! – astropanic

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