0

精度を上げるために、math/big.Rat を使用して数値を表現しました。Denom() は数値の分母を返し、Cmp() は 2 つの数値を比較します。どちらも純粋な読み取り専用関数のようです。しかし、データ競合を有効にしてコードを実行したところ、私の仮定全体が間違っていました。これらの関数が同じ Rat インスタンスで同時に呼び出されると、システムはデータ競合シナリオをスローします。これらの関数は読み取り専用ではありませんか?

私のテストケース

package main

import (
    "math/big"
    "sync"
)

func main() {
    x := big.NewRat(5, 1)
    wg := new(sync.WaitGroup)

    // just for testing
    for i := 0; i < 10; i++ {
        go func() {
            wg.Add(1)
            defer wg.Done()
            if i%2 == 0 {
                x.Cmp(x)
            } else {
                x.Denom()
            }
        }()
    }
    wg.Wait()
}

ソースを確認すると、 Denom() 関数が呼び出されるたびに、同じオブジェクトの値がリセットされます。これはソースの問題ですか?または、Rat Denom() と Cmp() を同時に使用しないでください。

ref の Golangからの Denom() ソース。

// Denom returns the denominator of x; it is always > 0.
   400  // The result is a reference to x's denominator; it
   401  // may change if a new value is assigned to x, and vice versa.
   402  func (x *Rat) Denom() *Int {
   403      x.b.neg = false // the result is always >= 0
   404      if len(x.b.abs) == 0 {
   405          x.b.abs = x.b.abs.set(natOne) // materialize denominator
   406      }
   407      return &x.b
   408  }

以下の議論に基づいて、さらにいくつかの点を追加します。意図した目的で変数「i」を使用する際に間違いを犯したことを認めます (ただし、データ競合シナリオを示すためにはそれでも機能します)。

ここでの私のポイントは、Denom() で実行される操作は、Rat によって表される値に変更を加えないということです。これは、値を表す Rat の作成時に実行するか、新しい値を Rat に設定することができます。私の懸念は、 Rat によって表される値が変更されない限り、同じ値の繰り返し計算 (同時安全ではない) です。では、なぜこれを作成/変更部分で行うことができないのでしょうか?

4

2 に答える 2

1

明確な競合状態があります。つまり、競合状態とは、3 つ以上の非同期ルーチン (スレッド、プロセス、コルーチンなど) がリソース (メモリまたは i/o デバイス) にアクセス (書き込みまたは読み取り) しようとしている場合です。そして、これらのルーチンの少なくとも 1 つには、書き込む意図があります。

あなたのケースでは、共有リソース ( var x Rat )にアクセスするゴルーチン作成しています。 . RWMutexでメモリを保護するだけです。

package main 

import (
    "math/big" 
    "sync"
)

func main() {
    x := big.NewRat(5, 1)
    wg := new(sync.WaitGroup)
    mutex := new(sync.RWMutex)

    wg.Add(10) // all goroutines

    for i := 0; i < 10; i++ {

        go func(index int) {
            defer wg.Done()

            if index%2 == 0 {

                mutex.RLock() // locks only for reading
                x.Cmp(x)
                mutex.RUnlock() // unlocks for reading

            } else {

                mutex.Lock() // locks for writing
                x.Denom()
                mutex.Unlock() // unlock for writing

            }
        }(i)
    }

    wg.Wait()
}

読み取り操作には RLock と RUnlock を使用し、書き込み操作には Lock と Unlock() を使用していることに注意してください。また、作成するゴルーチンの数がわかっているwg.Add(n)場合は、wg.Add(1) の後に go func(){...} を実行すると問題が発生するため、常に 1 行で実行することをお勧めします。

実際、ゴルーチン内でforインデックスを使用するというよくある間違いがあり、常にパラメーターとして渡します。

最後に、-raceフラグを使用して、次のようなコマンドを実行またはビルドすることをお勧めします。

go run -race rat.go

実際、 -raceを使用するだけで、あなたのコードと私のソリューションの違いがわかります

于 2016-10-25T18:28:52.290 に答える
0

Rat.Denom@JimBがコメントで指摘したように、スレッドセーフではないようです。標準ライブラリの一般的なルールは、明示的に記載されていない限り、メソッドはスレッド セーフではないということです。

あなたの他の問題は、閉鎖ループの一般的な落とし穴です。

この問題の説明については、この記事を参照してください。以下に示すように、変数をクロージャーに渡す必要があります。

作業例:

package main

import (
    "math/big"
    "sync"
    "fmt"
)

func main() {
    x := big.NewRat(5, 1)
    wg := new(sync.WaitGroup)

    // just for testing
    for i := 0; i < 10; i++ {
    wg.Add(1)
        go func(i int) {
            fmt.Println("x: ", x)
            defer wg.Done()
            if i%2 == 0 {
                fmt.Printf("i: %d, x.Cmp(x): %+v", i, x.Cmp(x))
            } else {
                fmt.Println("x.Denom(): ", x.Denom())
            }
        }(i)
    }
    wg.Wait()
}

実際の例については、https : //play.golang.org/p/aKo3gHuSeTを参照してください。

于 2016-10-25T15:56:34.847 に答える