gpt4 book ai didi

go - 有人可以向我解释一下这种 go​​lang 数据竞赛行为吗?

转载 作者:行者123 更新时间:2023-12-01 22:40:11 25 4
gpt4 key购买 nike

关闭。这个问题是not reproducible or was caused by typos .它目前不接受答案。












想改进这个问题?将问题更新为 on-topic对于堆栈溢出。

1年前关闭。




Improve this question




如果这是一个已经提出的问题,我不知道要查找什么,如果是,请提前抱歉。

我是golang的新手,我玩了一些goroutines,我在编译以下代码时发现:

package main

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

const (
jobCount = 5
threadCount = 1
)

type workerResult struct {
job int64
processId int
result *big.Int
}

func main() {
var hashMap sync.Map
jobs := make(chan int64, jobCount)
results := make(chan workerResult, jobCount)
var wg sync.WaitGroup

for i := 0; i < threadCount; i++ {
wg.Add(1)
go worker(jobs, results, i, &hashMap, &wg)
}

go func(){
for i := int64(0); i < jobCount; i++ {
jobs <- i
}
close(jobs)
}()

go func(){
wg.Wait()
close(results)
}()

for result := range results {
fmt.Println(result.job, result.processId, result.result.String())
}
}

func worker(jobs <-chan int64, results chan<- workerResult, id int, hashMap *sync.Map, wg *sync.WaitGroup) {
for i := range jobs {
results <- workerResult{i, id, fib(i, hashMap)}
}
(*wg).Done()
}

func fib(num int64, hashMap *sync.Map) *big.Int {
if num < 2 {
return big.NewInt(num)
} else {
hashedResult, ok := (*hashMap).Load(num)
if ok{
return hashedResult.(*big.Int)
} else {
result := fib(num-1, hashMap)
result = result.Add(result, fib(num-2, hashMap))
(*hashMap).Store(num, result)
return result
}
}
}


不知何故,编译器只会删除斐波那契算法的处理,并直接将最后一个输出值放入 if num < 2 的 else 子句。

输出如下所示:

0 0 0
1 0 1
2 0 4
3 0 4
4 0 4

如果我改变 jobCount对于别的东西,可以说 10 ,输出将是:

0 0 0
1 0 1
2 0 128
3 0 128
4 0 128
5 0 128
6 0 128
7 0 128
8 0 128
9 0 128

当我附加调试器并在任何地方放置断点时,我看到了不同的结果,仍然不好但不同:

0 0 0
1 0 1
2 0 1
3 0 2
4 0 4
5 0 8
6 0 32
7 0 64
8 0 64
9 0 128

所以后来我意识到,可能是编译器优化了可以优化的算法思想,所以我改变了struct workerResult并交换了 result 的类型(场)从 *big.Intstring ,留下这样的代码:

package main

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

const (
jobCount = 10
threadCount = 1
)

type workerResult struct {
job int64
processId int
result string
}

func main() {
var hashMap sync.Map
jobs := make(chan int64, jobCount)
results := make(chan workerResult, jobCount)
var wg sync.WaitGroup

for i := 0; i < threadCount; i++ {
wg.Add(1)
go worker(jobs, results, i, &hashMap, &wg)
}

go func(){
for i := int64(0); i < jobCount; i++ {
jobs <- i
}
close(jobs)
}()

go func(){
wg.Wait()
close(results)
}()

for result := range results {
fmt.Println(result.job, result.processId, result.result)
}
}

func worker(jobs <-chan int64, results chan<- workerResult, id int, hashMap *sync.Map, wg *sync.WaitGroup) {
for i := range jobs {
results <- workerResult{i, id, fib(i, hashMap).String()}
}
(*wg).Done()
}

func fib(num int64, hashMap *sync.Map) *big.Int {
if num < 2 {
return big.NewInt(num)
} else {
hashedResult, ok := (*hashMap).Load(num)
if ok{
return hashedResult.(*big.Int)
} else {
result := fib(num-1, hashMap)
result = result.Add(result, fib(num-2, hashMap))
(*hashMap).Store(num, result)
return result
}
}
}

我现在有很好的结果:
jobCount = 10
0 0 0
1 0 1
2 0 1
3 0 2
4 0 4
5 0 8
6 0 16
7 0 32
8 0 64
9 0 128
jobCount = 20
0 0 0
1 0 1
2 0 1
3 0 2
4 0 4
5 0 8
6 0 16
7 0 32
8 0 64
9 0 128
10 0 256
11 0 512
12 0 1024
13 0 2048
14 0 4096
15 0 8192
16 0 16384
17 0 32768
18 0 65536
19 0 131072

有人可以向我解释这种行为的原因吗,我怎么能期望这种情况发生,我怎么能强制编译器不这样做?

编辑:这不是编译器问题,而是数据竞争,在评论中指出,但我仍然不明白它是如何发生的,以及为什么第二版代码没有这种情况

使用 -race 运行代码参数给了我这个代码的第一个版本:

0 0 0
1 0 1
==================
WARNING: DATA RACE
Read at 0x00c000100048 by main goroutine:
math/big.(*Int).Text()
/usr/local/go/src/math/big/intconv.go:25 +0x3bd
math/big.(*Int).String()
/usr/local/go/src/math/big/intconv.go:40 +0x39f
main.main()
/home/illic/go/src/awesomeProject/fib.go:44 +0x221

Previous write at 0x00c000100048 by goroutine 7:
math/big.(*Int).Add()
/usr/local/go/src/math/big/int.go:121 +0x1e3
main.fib()
/home/illic/go/src/awesomeProject/fib.go:64 +0xfc
main.worker()
/home/illic/go/src/awesomeProject/fib.go:50 +0x56

Goroutine 7 (finished) created at:
main.main()
/home/illic/go/src/awesomeProject/fib.go:28 +0x195
==================
==================
WARNING: DATA RACE
Read at 0x00c000100040 by main goroutine:
math/big.(*Int).Text()
/usr/local/go/src/math/big/intconv.go:25 +0x3ec
math/big.(*Int).String()
/usr/local/go/src/math/big/intconv.go:40 +0x39f
main.main()
/home/illic/go/src/awesomeProject/fib.go:44 +0x221

Previous write at 0x00c000100040 by goroutine 7:
math/big.(*Int).Add()
/usr/local/go/src/math/big/int.go:132 +0x252
main.fib()
/home/illic/go/src/awesomeProject/fib.go:64 +0xfc
main.worker()
/home/illic/go/src/awesomeProject/fib.go:50 +0x56

Goroutine 7 (finished) created at:
main.main()
/home/illic/go/src/awesomeProject/fib.go:28 +0x195
==================
2 0 4
3 0 4
4 0 4
Found 2 data race(s)

对于代码的第二个版本,这是输出:

0 0 0
1 0 1
2 0 1
3 0 2
4 0 4

编辑2:

在对评论的建议和唯一的答案之后,我意识到问题在于我如何处理添加,我使用的是存储在 hashmap 中的指针,所以我基本上是在每次新执行时破坏以前计算中存储的值,解决方案是在内存中为该计算实际分配一个新空间,这个版本的代码应该没有竞争条件:

package main

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

const (
jobCount = 10
threadCount = 1
)

type workerResult struct {
job int64
processId int
result *big.Int
}

func main() {
var hashMap sync.Map
jobs := make(chan int64, jobCount)
results := make(chan workerResult, jobCount)
var wg sync.WaitGroup

for i := 0; i < threadCount; i++ {
wg.Add(1)
go worker(jobs, results, i, &hashMap, &wg)
}

go func(){
for i := int64(0); i < jobCount; i++ {
jobs <- i
}
close(jobs)
}()

go func(){
wg.Wait()
close(results)
}()

for r := range results {
fmt.Println(r.job, r.processId, r.result.String())
}
}

func worker(jobs <-chan int64, results chan<- workerResult, id int, hashMap *sync.Map, wg *sync.WaitGroup) {
for i := range jobs {
results <- workerResult{i, id, fib(i, hashMap)}
}
(*wg).Done()
}

func fib(num int64, hashMap *sync.Map) *big.Int {
if num < 2 {
return big.NewInt(num)
} else {
hashedResult, ok := (*hashMap).Load(num)
if ok{
return hashedResult.(*big.Int)
} else {
result := big.Int{}
result.Set(fib(num-1, hashMap))
result = *result.Add(&result, fib(num-2, hashMap))
(*hashMap).Store(num, &result)
return &result
}
}
}

最佳答案

big.Int 的文档说:

An Int represents a signed multi-precision integer. The zero value for an Int represents the value 0.

Operations always take pointer arguments (*Int) rather than Int values, and each unique Int value requires its own unique *Int pointer. To "copy" an Int value, an existing (or newly allocated) Int must be set to a new value using the Int.Set method; shallow copies of Ints are not supported and may lead to errors.


所以,我们将使用 Int.Set在我们的程序中删除竞争条件。
package main

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

const (
jobCount = 5
threadCount = 1
)

type workerResult struct {
job int64
processID int
result *big.Int
}

func main() {
var hashMap sync.Map
jobs := make(chan int64, jobCount)
results := make(chan workerResult, jobCount)
var wg sync.WaitGroup

for i := 0; i < threadCount; i++ {
wg.Add(1)
go worker(jobs, results, i, &hashMap, &wg)
}

go func() {
for i := int64(0); i < jobCount; i++ {
jobs <- i
}
close(jobs)
}()

go func() {
wg.Wait()
close(results)
}()

for r := range results {
fmt.Println(r.job, r.processID, r.result.String())
}
}

func worker(jobs <-chan int64, results chan<- workerResult, id int, hashMap *sync.Map, wg *sync.WaitGroup) {
for i := range jobs {
results <- workerResult{i, id, fib(i, hashMap)}
}
wg.Done()
}

func fib(num int64, hashMap *sync.Map) *big.Int {
if num < 2 {
z := big.Int{}
return z.Set(big.NewInt(num))
}
hashedResult, ok := hashMap.Load(num)
if ok {
z := big.Int{}
return z.Set(hashedResult.(*big.Int))
}
result := fib(num-1, hashMap)
result = result.Add(result, fib(num-2, hashMap))
hashMap.Store(num, result)
z := big.Int{}
return z.Set(result)
}
看看,如果这个程序对你有帮助!

关于go - 有人可以向我解释一下这种 go​​lang 数据竞赛行为吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62249381/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com