Multithreading Go指针多线程读写错误

Multithreading Go指针多线程读写错误,multithreading,pointers,go,unsafe,Multithreading,Pointers,Go,Unsafe,正常应为恒定输出 test1 test2 ........ 但只有test1输出,程序挂起,没有响应 指针的赋值是最基本的操作,这应该是线程安全的,以满足周期要求 但这项测试一直未能成功 type Point struct { X int Y int } func main() { var p *Point = nil test := true go func() { for test { if tmp := p

正常应为恒定输出

test1
test2
........
但只有test1输出,程序挂起,没有响应 指针的赋值是最基本的操作,这应该是线程安全的,以满足周期要求 但这项测试一直未能成功

type Point struct {
    X int
    Y int
}

func main() {
    var p *Point = nil
    test := true
    go func() {
        for test {
            if tmp := p; tmp == nil {
                p = &Point{}
            }
        }
    }()
    go func() {
        for test {
            if tmp := p; tmp != nil {
                p = nil
            }
        }
    }()

    n := 0
    for test {
        n++
        fmt.Printf("testing%v....\r\n",n)
        time.Sleep(1000 * time.Millisecond)
    }

    fmt.Printf("test fail")
}
代码:

如果将指针更改为int,这很好

func main() {
    var p int = 0
    test := true
    go func() {
        for test {
            if tmp := p; tmp == 0 {
                p = 1
            }
        }
    }()
    go func() {
        for test {
            if tmp := p; tmp != 0 {
                p = 0
            }
        }
    }()

    n := 0
    for test {
        n++
        fmt.Printf("testing%v....\r\n",n)
        time.Sleep(1000 * time.Millisecond)
    }

    fmt.Printf("test fail")
}

问题不仅在于有比赛,而且当有比赛时,日程安排会有问题。下面的代码具有一些选项,这些选项将使该代码生成所需的输出(已注释)。所以你要么:

  • 添加一个锁,以[正确的方式]修复竞态条件
  • 在每项比赛常规中增加一点睡眠-比赛继续进行
  • 此代码

    package main
    
    import (
        "fmt"
        "runtime"
        "sync"
        "time"
    )
    
    type Point struct {
        X int
        Y int
    }
    
    var lock sync.Mutex
    
    func main() {
        // runtime.GOMAXPROCS(4)
        fmt.Println("Max # of parallel processes = ", MaxParallelism())
    
        var p *Point = nil
        test := true
        go func() {
            for test {
                // lock.Lock()
                if tmp := p; tmp == nil {
                    p = &Point{}
                }
                // lock.Unlock()
                // time.Sleep(1 * time.Nanosecond)
    
            }
        }()
        go func() {
            for test {
                // lock.Lock()
                if tmp := p; tmp != nil {
                    p = nil
                }
                // lock.Unlock()
                // time.Sleep(1 * time.Nanosecond)
            }
        }()
    
        n := 0
        for test {
            n++
            fmt.Printf("testing%v....\r\n", n)
            time.Sleep(1000 * time.Millisecond)
        }
    
        fmt.Printf("test fail")
    }
    
    func MaxParallelism() int {
        maxProcs := runtime.GOMAXPROCS(0)
        numCPU := runtime.NumCPU()
        if maxProcs < numCPU {
            return maxProcs
        }
        return numCPU
    }
    

    尽管有4个虚拟处理器,但问题似乎在于主处理器在第一次睡眠后从未被调度。显然,当有比赛条件的时候,这是莫名其妙地(至少对我来说)抬起了它丑陋的头。另一个观察结果是,如果我们减少两个goroutine中的一个,并且继续修改main循环中的p变量,那么它就可以工作了。所有这些似乎都给人一种印象,即当存在竞争条件时,运行时处理的行为是不可预测的。

    您的代码是快速的,因此未定义。不能同时写入同一变量。无需争论。使用并发时,请始终使用种族检测器运行测试(
    go test-race
    ),因为您可能会对同一指针进行并发分配,这很糟糕。这就是为什么golang有
    select
    、频道和
    sync
    包,其中包含有用的小曲,如
    sync.Mutex
    。另外:
    var p*Point=nil
    可以缩短为
    var p*Point
    谢谢。我现在知道如何处理这个问题了。这种行为的后果目前尚不清楚。整个应用程序被挂起,没有响应,这不利于发现问题。代码中有2个忙循环。不管比赛情况如何,这都会造成问题。谢谢。你的理解很好,我现在知道如何处理这个问题了。我想说的是,如果现在存在竞争,这种行为的后果是不可预测的。但是,整个应用程序被挂起,没有响应,这不利于处理问题。time.Sleep只解决了出错的概率,无法解决问题。locker是一个可行的选项,这就是为什么我提到锁定是正确的方法(正如@Volker评论中所暗示的)。上面的代码还用于演示不使用正确的同步原语的危险。
    go run main.go 
    Max # of parallel processes =  4
    testing1....