16

この質問に対する彼の答えの中で: Golang for Windowsの不安定な動作? ユーザー@distributedは、同時実行のgoroutineで共有変数へのアクセスをロック/同期することをお勧めします。

どうやってやるの?

この問題の詳細:

このコード(クロージャーがオンになっている戻り関数views)を複数のゴルーチンで同時に実行します。

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 1
    return func(c *http.Conn, r *http.Request) {
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views)
        views++
    }
}

IO関数には時間がかかるようです。その結果、次のような出力が得られます。

Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 11 so far.

正常にインクリメントしますが、印刷されると、printing+incrementingの操作がまったくアトミックではないことがわかります。

次のように変更した場合:

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 0
    return func(c *http.Conn, r *http.Request) {
        views++
        // I can only hope that other goroutine does not increment the counter 
        // at this point, i.e., right after the previous line and before the 
        // next one are executed!
        views_now := views
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

正常に動作しているようですが、最終的に失敗しないかどうかは完全にはわかりません...

4

2 に答える 2

25

同期されたカウンターだけが必要な場合sync.Mutexは、標準的なソリューションを使用します。同期/アトミックパッケージは、低レベルのもの、または深刻なパフォーマンスの問題を測定した場合にのみ使用する必要があります。

type Counter struct {
    mu  sync.Mutex
    x   int64
}

func (c *Counter) Add(x int64) {
    c.mu.Lock()
    c.x += x
    c.mu.Unlock()
}

func (c *Counter) Value() (x int64) {
    c.mu.Lock()
    x = c.x
    c.mu.Unlock()
    return
}

func makeHomeHandler() func(c http.ResponseWriter, r *http.Request) {
    var views Counter
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views.Value())
        views.Add(1)
    }
}

特定の問題については、クロージャを返すのではなく、http.Handlerインターフェイスを満たす新しいタイプを定義することをお勧めします。それも簡単に見えます:

type homeHandler struct {
    mu  sync.Mutex
    views   int64
}

func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.mu.Lock()
    defer h.mu.Unlock()
    fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], h.views)
    h.views++
}

func init() {
    http.Handle("/", new(homeHandler))
}
于 2012-05-24T10:34:12.113 に答える
10

同期パッケージには、いくつかの同期プリミティブがあります。問題に応じて、RWMutexまたはプレーンMutexを使用できます。

より具体的な回答が必要な場合は、その目的についてさらに情報を提供してください。

編集:リンクされた質問を読んだ後、おそらく同期/アトミックを探していますが、Mutexも問題ありません。

Edit2:あなたが例であなたの投稿を更新したのを見ました。これがsync/atomicを使用したコードです。

func makeHomeHandler() func(w http.ResponseWriter, r *http.Request) {
    var views *uint64 = new(uint64)
    atomic.StoreUint64(views, 0) // I don't think this is strictly necessary
    return func(w http.ResponseWriter, r *http.Request) {
        // Atomically add one to views and get the new value
        // Perhaps you want to subtract one here
        views_now := atomic.AddUint64(views, 1) 
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

(注:私は上記をテストしていないので、タイプミス/ブレインファートがある可能性があります)私は今それをテストしました。

于 2012-05-23T23:07:54.830 に答える