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

ロジックには 2 つの異なる問題があります。1 つ目はスライス自体の操作方法であり、2 つ目は実際の同時実行の問題に関するものです。

スライス操作の場合、パラメーターとしてスライスを値で渡すだけでは、スライスを成長させる必要がある場合やバッキング配列を再割り当てする必要がある場合に、呼び出しサイトがスライスを認識する方法でスライスを変更することはできません。追加する新しいデータに対応します。これを処理するには、2 つの一般的な方法があります。

新しいスライスを返すことにより:

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

2 番目の問題については、安全でない方法で値が同時に変更されていないことを確認する必要があります。それに対処する方法もたくさんあります。

チャネルを使用する

部屋を直接操作する代わりに、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たとえば、 )。

排他的 (保護された) 領域にパニックを起こしやすいロジックがある場合は、Unlock呼び出しの直後に呼び出しを延期することをお勧めしLockます。多くの人は、単純な操作であっても、安全かどうかを判断する必要がないように、単純に次から次へと配置します。よくわからない場合は、それが良い考えかもしれません。

非常に重要:ほとんどの場合、値によってミューテックス フィールドを持つ構造体をコピーするのは悪い考えです。代わりに、元の値へのポインタを使用してください。この理由は、アトミック操作が正しく機能するために、mutex がそのフィールドのアドレスを変更しないことに内部的に依存しているためです。

グローバル ミューテックスを追加する

処理しようとしているケースにはおそらく当てはまらないが、知っておくと便利な、より異常な状況では、データを保護する代わりにロジック自体を保護することを選択できます。これを行う 1 つの方法は、次の行の周りにあるグローバル ミューテックス変数を使用することです。

var addWindowMutex sync.Mutex

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

この方法addWindow自体は、誰が呼び出しても保護されています。このアプローチの利点は、room の実装に依存しないことです。不利な点は、並行して処理されるルームの数に関係なく、単一のゴルーチンのみが排他的領域に入ることです (これは以前のソリューションには当てはまりません)。

これを行うときは、その間に変更する並行性がまだ残っている場合に備えて、読み取り room.Windowsまたは排他的領域で変更されているデータも保護する必要があることに注意してください。

最後に、プロンプトなしのフィードバックと同じように、これらのエラー値を確認してください。エラーを無視することは、それが単なる例であろうと深刻なコードであろうと、本当に悪い習慣です。このようなサンプル コードを作成しても、多くの場合、エラーが発生します。

于 2013-08-27T15:07:46.440 に答える