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

标签 go

关闭。这个问题是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/

相关文章:

debugging - 有没有办法为链接到 Go 的 CGO 代码获取调试符号?

struct - 如何在控制台中打印结构变量?

go - 特定错误处理的不明确行为

go - 如何在 *tls.Conn 上设置 SetKeepAlivePeriod

performance - 没有外部依赖的高性能网络蜘蛛

go - 无法使用 baseURL(类型 *url.URL)作为 http.Get 参数中的字符串类型

json - 是否可以将 "Omitempty"json 标记作为默认行为?

rest - 在 REST 查询中指定数据结构

go - 如何访问所有连接的 tcp 客户端的变量?

string - 在 Golang 中最后一次出现模式后解析提取 header 的 HTML 模板