在big.Rat中的数据竞争问题

huangapple go评论81阅读模式
英文:

Data race in big.Rat

问题

我使用math/big.Rat来表示数字以提高精度。Denom()函数返回数字的分母,Cmp()函数用于比较两个数字。这两个函数似乎都是纯读取函数。但是当我在启用数据竞争检测的情况下运行我的代码时,我的整个假设都是错误的。当这些函数与相同的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()函数?

Denom()函数的源代码如下:

// Denom returns the denominator of x; it is always > 0.
func (x *Rat) Denom() *Int {
	x.b.neg = false // the result is always >= 0
	if len(x.b.abs) == 0 {
		x.b.abs = x.b.abs.set(natOne) // materialize denominator
	}
	return &x.b
}

根据下面的讨论,我添加了一些额外的观点,并且我承认在使用变量'i'时犯了一个错误(但它仍然可以用于显示数据竞争的情况)。

我的观点是在Denom()函数中执行的操作不会修改Rat表示的值。这可以在创建Rat以表示一个值或在Rat中设置新值时执行。我的担忧是除非Rat表示的值发生更改,否则会重复计算相同的值(不是并发安全的)。那么为什么不能在创建/修改部分进行这个操作呢?

英文:

I used math/big.Rat to represent numbers for accuracy.
Denom() returns the denominator of number and Cmp() to compare two numbers. Both of them seems to be a pure read only functions. But when I ran my code with data race enabled, my whole assumption went wrong. When these functions are called concurrently with the same Rat instance, system throws data race scenario. Are these functions not read-only?

my test case

package main

import (
	&quot;math/big&quot;
	&quot;sync&quot;
)

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

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

When I check the source, every time Denom() function is called, it resets value in the same object. Is this an issue in the source? or I shouldn't take use Rat Denom() and Cmp() concurrently.

Denom() source from Golang for ref.

// Denom returns the denominator of x; it is always &gt; 0.
   400	// The result is a reference to x&#39;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 &gt;= 0
   404		if len(x.b.abs) == 0 {
   405			x.b.abs = x.b.abs.set(natOne) // materialize denominator
   406		}
   407		return &amp;x.b
   408	}

I am adding few more points based on the discussions below and I accept that I made a mistake in using the varibale 'i' for the intended purpose (But still it will work to show the Data race scenario).

My point here is the operation performed in the Denom() won't have modification in the value represented by Rat. This can be performed upon creation of Rat to represent a value or a new value is set in the Rat. My concern is the repeated computaion (not concurrent safe) of same value again and again unless the value represented by the Rat is changed. Then why can't this be done at the creation/modifcation part?

答案1

得分: 1

你有一个明显的竞态条件,简而言之,竞态条件是指当两个以上的异步例程(线程、进程、协程等)试图访问(写入或读取)一个资源(内存或I/O设备),并且至少有一个例程有写入意图时发生的情况。

在你的情况下,你创建了访问共享资源(var x Rat)的goroutine,正如你可能注意到的,方法**Denom()**在第403行和第405行修改了自己的值,而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) // 所有的goroutine

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

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

            if index%2 == 0 {

                mutex.RLock() // 仅用于读取的锁
                x.Cmp(x)
                mutex.RUnlock() // 解锁读取

            } else {

                mutex.Lock() // 用于写入的锁
                x.Denom()
                mutex.Unlock() // 解锁写入

            }
        }(i)
    }

    wg.Wait()
}

请注意,我们在读取操作中使用了RLock和RUnlock,而在写入操作中使用了Lock和Unlock()。此外,如果你知道要创建的goroutine数量,我总是建议在一行中执行wg.Add(n),因为如果你在wg.Add(1)之后执行go func(){...},会遇到问题。

此外,你确实犯了一个常见的错误,即在goroutine中使用for索引,应该将它们作为参数传递。

最后,我建议你在go rungo build命令中使用**-race**标志,例如:

go run -race rat.go

实际上,你将会看到使用**-race**时你的代码和我的解决方案之间的区别。

英文:

You have a clear race condition, in short a race condition is when more than two asynchronous routines (threads, process, co-routines. etc.) are trying to access(write or read) a resource(memory or i/o devices) and at least one of those routines has intentions to write.

In your case you're creating goroutines that are accessing a shared resource (var x Rat) and as you may note the method Denom() modifies its own value in line 403 and 405 and it seems that the Cmp() method is only reading. All we have to do is protect the memory with a RWMutex:

package main 

import (
    &quot;math/big&quot; 
    &quot;sync&quot;
)

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

    wg.Add(10) // all goroutines

    for i := 0; i &lt; 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()
}

Note that we're using RLock and RUnlock for reading operation and Lock and Unlock() for write. Also if you know the number of goroutines to be created I always recommend do the wg.Add(n) in just one line because if you do after wg.Add(1) the go func(){...} you'll be in troubles.

And indeed you have a common mistake of using a for index inside a goroutine, always pass them as parameters.

Finally I recommend you to use the -race flag to go run or go build commands, like:

go run -race rat.go

In fact you will see the difference between your code and my solution just using -race

答案2

得分: 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()
}

参见:这里有一个实时示例。

英文:

Rat.Denom appears not to be thread safe, as @JimB pointed out in the comment. The general rule with the standard library is that methods are not thread safe unless explicitly noted.

Your other issue is a common pitfall with closure loops.

See this article for a description of the issue. You must pass the variable into the closure, as seen below.

Working example:

package main

import (
    &quot;math/big&quot;
    &quot;sync&quot;
	&quot;fmt&quot;
)

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

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

See: https://play.golang.org/p/aKo3gHuSeT for a live example.

huangapple
  • 本文由 发表于 2016年10月25日 23:48:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/40244358.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定