首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >big.Rat中的数据竞赛

big.Rat中的数据竞赛
EN

Stack Overflow用户
提问于 2016-10-25 23:48:28
回答 2查看 149关注 0票数 0

为了准确起见,我使用了数学/big.rate来表示数字。Denom()返回number和Cmp()的分母以比较两个数字。这两个函数似乎都是纯只读函数。但是,当我在启用数据竞争的情况下运行代码时,我的整个假设都出错了。当这些函数与相同的Rat实例并发调用时,系统会抛出数据争用情况。这些函数不是只读的吗?

我的测试案例

代码语言:javascript
复制
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()。

Denom()源码来自Golang for ref

代码语言:javascript
复制
// 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表示的值发生了变化。那么为什么不能在创建/修改部分完成呢?

EN

回答 2

Stack Overflow用户

发布于 2016-10-26 02:28:52

您有一个明确的竞争条件,简而言之,竞争条件是指两个以上的异步例程(线程、进程、协同例程。等)试图访问(写入或读取)资源(存储器或i/o设备)并且这些例程中的至少一个具有写入意图。

在您的示例中,您正在创建访问共享资源的goroutine(变量x Rat),并且您可能注意到Denom()方法在第403和405行修改了自己的值,并且Cmp()方法似乎只是读取的。我们所要做的就是用RWMutex保护内存

代码语言:javascript
复制
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()进行写操作。此外,如果您知道要创建的goroutines的数量,我总是建议您只在一行中执行wg.Add(n),因为如果您在wg.Add(1)之后执行go func(){...},您将遇到麻烦。

事实上,你有一个常见的错误,在goroutine中使用作为索引,总是将它们作为参数传递。

最后,我建议您使用-race标志来运行go 或go build命令,如下所示:

go run -race rat.go

实际上,只要使用-race,您就会看到您的代码和我的解决方案之间的区别

票数 1
EN

Stack Overflow用户

发布于 2016-10-25 23:56:35

正如@JimB在评论中指出的那样,Rat.Denom似乎不是线程安全的。标准库的一般规则是,除非明确说明,否则方法不是线程安全的。

您的另一个问题是闭包循环的常见陷阱。

有关此问题的说明,请参阅this article。您必须将变量传递到闭包中,如下所示。

工作示例:

代码语言:javascript
复制
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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40244358

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档