数据竞赛在大鼠



我用math/big。鼠来表示数字的准确性。dom()返回number的分母,Cmp()用于比较两个数字。它们似乎都是纯只读函数。但是当我在启用数据竞争的情况下运行代码时,我的整个假设都错了。当这些函数与同一个Rat实例并发调用时,系统会引发数据竞争场景。这些函数不是只读的吗?

my test case

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

当我检查源代码时,每次调用dom()函数时,它都会重置同一对象中的值。这是源代码中的问题吗?否则我不应该同时使用Rat dom()和Cmp()。

Denom() source from Golang for ref.

// 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 '时犯了一个错误(但它仍然可以显示数据竞争场景)。

我想说的是,在dom()中执行的操作不会对Rat表示的值进行修改。这可以在创建Rat时执行,以表示一个值,或者在Rat中设置一个新值。我担心的是重复计算(不是并发安全的)相同的值一次又一次,除非鼠表示的值被改变。那么为什么不能在创建/修改部分完成呢?

你有一个明确的竞争条件,简而言之,竞争条件是当两个以上的异步例程(线程,进程,协例程)。等)正在尝试访问(写或读)资源(内存或I/O设备),并且这些例程中至少有一个有写意图。

在您的情况下,您正在创建访问共享资源的例程(var x Rat),并且正如您可能注意到的方法 dom ()403405中修改自己的值,似乎Cmp()方法只是读取。我们所要做的就是用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(){…你会有麻烦的。

确实你有一个常见的错误,在一个程序中使用作为索引,总是把它们作为参数传递。

最后,我建议您使用-race标志来go rungo build命令,如:

go run -race rat.go

事实上,你会看到你的代码和我的解决方案之间的区别只是使用-race

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

相关内容

  • 没有找到相关文章

最新更新