2016-06-23 9 views
0

私は、(可能な限り多くの)マップエントリを範囲指定してチャネルに送信する必要があるシナリオを持っています。チャネルのもう一方の端での操作には長い時間がかかり、マップは同時にアクセスされます(RWMutexによって保護されます)。地図はかなり大きく、私はそれの一時的なコピーを作成しないようにしたい。マップエントリを同時にチャネルに読み込みます

は、私はこのような構造体があるとします。

type Example struct { 
    sync.RWMutex 
    m map[string]struct{} 
} 

は今、私はこのような何かを思い付いた:

func (e *Example) StreamAll() <-chan string { 
    toReturn := make(chan string) 
    go func() { 
     e.RLock() 
     defer e.RUnlock() 
     for k := range e.m { 
      e.RUnlock() 
      toReturn <- k 
      e.RLock() 
     } 
     close(toReturn) 
    }() 
    return toReturn 
} 

language specificationは、マップ上までについてのこの興味深いビットを持っています

まだ到達していないマップエントリが反復処理中に削除された場合、対応する反復値は削除されません生産される。反復中にマップエントリが作成された場合、そのエントリは反復中に生成されるか、スキップされる可能性があります。

ここで私が知りたいのは、マップ間の距離が変更されても、マップ上の範囲指定方法が機能するという保証はありますか?最後に読み込んだキーが削除された場合も含めて削除されますか?私はすべての地図の項目が必要なわけではありませんが、ほとんどは地図の項目です。あなたが鍵を生成するとしてあなたのマップが変更され、あなたが得るよう:これはあなたが持っているものである

0)一貫性のないスナップショット

package main 

import (
    "fmt" 
    "sync" 
) 

type Example struct { 
    sync.RWMutex 
    m map[string]struct{} 
} 

func NewExample() *Example { 
    return &Example{ 
     m: make(map[string]struct{}), 
    } 
} 

func (e *Example) Put(s string) { 
    e.Lock() 
    defer e.Unlock() 
    e.m[s] = struct{}{} 
} 

func (e *Example) Delete(s string) { 
    e.Lock() 
    defer e.Unlock() 
    delete(e.m, s) 
} 

func (e *Example) StreamAll() <-chan string { 
    toReturn := make(chan string) 
    go func() { 
     e.RLock() 
     defer e.RUnlock() 
     for k := range e.m { 
      e.RUnlock() 
      toReturn <- k 
      e.RLock() 
     } 
     close(toReturn) 
    }() 
    return toReturn 
} 

func main() { 
    e := NewExample() 
    e.Put("a") 
    e.Put("b") 

    values := e.StreamAll() 
    // Assume other goroutines concurrently call Put and Delete on e 
    for k := range values { 
     fmt.Println(k) 
    } 
} 
+0

あなたのプレイグランドへのリンクは動作していません。 – OneOfOne

+0

ありがとう、私は今質問にそれを引っ張った。 – mrd0ll4r

+0

'range'アクション自体は並行安全な方法で実行する必要があります。マップが変更されている場合は、要素の奇妙なシーケンスが得られる可能性があります(マップ上を移動するたびに、 –

答えて

1

が、私は3つの選択肢を参照してください。ここで

は完全な例でありますあなたが得るもの。あなたのロックが正しいことは完全にはわかりません。それは本当に私には疑わしい。もちろん、レースディテクタで広範囲にテストしてください。

1)"Stop The World" - すべてのキーを生成している間は、マップへの書き込みアクセスをブロックできます。キーを生成することは、アイテムを処理するよりもはるかに迅速であり、処理するアイテムの完璧な整合性のあるスナップショットを提供します。残念なことに、コルーチンにキーを送信すると、そのキーが処理されるまでにそれらのキーが存在しないことがあります。あなたはそれでOKだと思います。すべてのキーのコピーを保存する必要があるので、うまくいけばOKです。

2)MVCC(マルチバージョン同時実行制御)をロールバックする - 1つのマップを使用する代わりに、2を使用します。AとBとしましょう。最初のマップに書き込むのは、 2番目のマップ、次にロールをフリップします。

  • RWロックの隣に、ブール値を追加してマップを保護します。
  • 通常どおりRWロックを取り出しますが、次に両方のマップを参照してください。
  • ブール値が真の場合、Aからの読み込み/書き込みが許可されますが、Aで見つからない場合はBへのフォールバックが許可されます。
  • ブール値がfalseの場合はBからの読み書きBで見つからない場合はAに「フォールバック」します。

バックグラウンドジョブを開始するときは、ブール値を反転しながらRWロックを取り出してください。今度は、 "フォールバック"マップのキーを繰り返して、それぞれにgouroutineを呼び出します。誰もその地図に書き込んでいないので、キーは存在することが保証されています。

goroutineのBから削除することができます(読み込み時と削除中にロ​​ックを使用する必要があります)。

しかし、その後、「Bを実行してBを一掃=行い、その後、すべてのゴルーチンが行われるのを待ち、(すべてがちょうど読んでいるので)ちょうどNOロックしてすべてのエントリを処理する方が簡単/良いかもしれません() "を使用して空のマップを取得します。これは一度にすべてのあなたの記憶を解放し、これまでに削除する必要があるいくつかの会計を保存します。

マップを消去した(またはすべてのエントリを削除した)と、ブール値を反転している間にRWロックを取り、別のマップの処理を開始できます。

アイテムを頻繁に更新すると、マップのコピーが2つになるという欠点があります。この場合、1)WRITESにフォールバックマップを確認させる。存在する場合は更新し、そうでない場合はメインマップを更新します。 2)背景処理の前に地図から項目を削除します。 (一括削除のトリックは使用できません)

+0

この詳細な回答ありがとうございます! OP、私はゴランヌにこれを投稿したしばらくすると、これがスレッドです(https://groups.google.com/forum/#!topic/Golang-nuts/vkysJuKen1A)。彼らはあなたと同様の点を作っています。実際には、Erlangの解決策や2)で説明したようなことをする必要があります。現時点では、プロジェクトはまだ進んでいませんが、私が実際に使っていたロックが本当にうまく動作するかどうかを知りたいのですが... – mrd0ll4r

関連する問題