Golang代码执行结果竟如此惊人
Golang代码执行结果竟如此惊人
package main
import (
"time"
"fmt"
)
func in(d *[]int) {
fmt.Println(*d)
time.Sleep(time.Duration(2000000000))
fmt.Println(*d)
}
func main() {
a := []int{1,2,3}
go in(&a)
a = []int{4,5,6}
//fmt.Printf("")
for {
}
}
代码执行结果如下: [1 2 3] [1 2 3]
当移除注释"fmt.Printf("")“的”//"时,代码执行结果变为: [4 5 6] [4 5 6]
更多关于Golang代码执行结果竟如此惊人的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这个假设似乎是正确的,但我们应该搜索一些信息来确认这一点。
更多关于Golang代码执行结果竟如此惊人的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
如果我不这样写会发生什么。
任何情况都可能发生。这是未定义的行为。
我知道标准做法应该是使用锁等同步机制来确保程序的正确执行,但我只是想知道如果不这样写会发生什么情况。
pamleft:
我只是想知道如果不按照这种方式写会发生什么。
你可能会得到不可预测的结果。就是这样,在并发编程中如果你想要特定的执行顺序,必须自己来实现。
我理解这个"并发"概念。我已经尝试了很多次,无论是否使用printf,结果都是一样的。我猜测某些涉及系统调用的goroutine具有更高的优先级,但我未能找到关于这个话题的相关信息。
根据您的说法,结果应该与现实情况相反。而且打印语句被放置在赋值操作之后,这意味着指令被重新排序了。
我猜想某些涉及系统调用的goroutine具有较高优先级,但我未能找到关于该主题的相关信息。
通常情况下,Go 运行时仅在 I/O 边界处切换协程。运行时可能会意识到主协程中不再发生任何 I/O 操作,因此直接切换到另一个协程,并且永远不会再重新考虑主协程。
不过这只是个假设。
这被称为"并发"。
你永远无法知道哪个 Go 协程会先运行,以及运行时在它们之间如何切换。
在不同的计算机上,打印输出可能会有所不同。
如果你想要严格的边界控制,根据具体需求,你需要使用互斥锁、等待组、信号量,甚至是裸通道。
func main() {
fmt.Println("hello world")
}
func main() {
fmt.Println("hello world")
}
当你添加fmt.Printf时,主goroutine在等待标准输出打印时会出现轻微暂停。这使得另一个goroutine能够在进入紧密的for循环之前运行,并获取由&a指向的新数组。
但我同意这个观点。在多个goroutine共享数据时,如果不使用某种同步机制就不应该进行写入操作。即使现在能正常工作,但在运行速度稍快或稍慢的计算机上,或者核心数与你电脑不同的设备上,可能就无法正常工作了。
这真是令人着迷,我不知道为什么会发生这种情况。我最初的想法是变量没有被正确引用,或者存在某种并发问题。但是,这个问题只出现在"for"无限循环中。如果我在底部放置一个睡眠计时器而不是无限循环,问题就会消失。
使用睡眠时间而非无限循环
https://goplay.space/#gMrEqQ0f_Tb
我认为这不太重要,因为无限循环在实践中是有问题的,而且代码本身无法运行。如果能找到某种遇到这种情况的实际用例,那将会很有趣。
如果你想知道如何修复并发问题,那么应该使用WaitGroup,它会在goroutine成功运行之前保持程序,然后再继续执行其余代码。我有一个使用和不使用WaitGroup的示例。
不使用WaitGroup
https://goplay.space/#FRLTD1GqkQ3
使用WaitGroup
https://goplay.space/#OBGVQJT7KhJ
如果你需要防止竞态条件(这是导致此问题从"[1 2 3]“变为”[4 5 6]"的根本原因),那么你需要实现一个互斥锁来在继续之前锁定变量。我也有一个这样的示例。祝你好运。
这是一个典型的并发数据竞争问题,涉及到Go语言的内存模型和编译器优化。让我详细分析一下这个现象:
问题分析
当注释掉 fmt.Printf("") 时,编译器可能会进行优化,导致goroutine看到的是切片 a 的旧值。而加上 fmt.Printf("") 后,这个函数调用阻止了某些优化,使得goroutine能够看到切片 a 的新值。
根本原因
这里存在数据竞争(data race),因为主goroutine和启动的goroutine在没有同步机制的情况下并发访问同一个切片。
package main
import (
"time"
"fmt"
"sync"
)
func in(d *[]int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println(*d)
time.Sleep(2 * time.Second)
fmt.Println(*d)
}
func main() {
var wg sync.WaitGroup
a := []int{1,2,3}
wg.Add(1)
go in(&a, &wg)
// 这里存在数据竞争
a = []int{4,5,6}
wg.Wait()
}
正确解决方案
方案1:使用互斥锁
package main
import (
"time"
"fmt"
"sync"
)
type SafeSlice struct {
mu sync.RWMutex
data []int
}
func in(s *SafeSlice, wg *sync.WaitGroup) {
defer wg.Done()
s.mu.RLock()
fmt.Println(s.data)
s.mu.RUnlock()
time.Sleep(2 * time.Second)
s.mu.RLock()
fmt.Println(s.data)
s.mu.RUnlock()
}
func main() {
var wg sync.WaitGroup
s := &SafeSlice{data: []int{1,2,3}}
wg.Add(1)
go in(s, &wg)
s.mu.Lock()
s.data = []int{4,5,6}
s.mu.Unlock()
wg.Wait()
}
方案2:使用通道进行通信
package main
import (
"time"
"fmt"
)
func in(ch <-chan []int, done chan<- bool) {
data := <-ch
fmt.Println(data)
time.Sleep(2 * time.Second)
data = <-ch
fmt.Println(data)
done <- true
}
func main() {
ch := make(chan []int, 2)
done := make(chan bool)
go in(ch, done)
ch <- []int{1,2,3}
ch <- []int{4,5,6}
<-done
}
方案3:使用原子操作(适用于简单类型)
package main
import (
"time"
"fmt"
"sync/atomic"
)
type AtomicSlice struct {
ptr atomic.Value
}
func (a *AtomicSlice) Store(slice []int) {
a.ptr.Store(slice)
}
func (a *AtomicSlice) Load() []int {
return a.ptr.Load().([]int)
}
func in(a *AtomicSlice, done chan bool) {
fmt.Println(a.Load())
time.Sleep(2 * time.Second)
fmt.Println(a.Load())
done <- true
}
func main() {
a := &AtomicSlice{}
done := make(chan bool)
a.Store([]int{1,2,3})
go in(a, done)
a.Store([]int{4,5,6})
<-done
}
关键要点
- 避免数据竞争:在并发环境中访问共享数据必须使用同步机制
- 不要依赖未定义行为:原代码的执行结果依赖于编译器的具体实现
- 使用正确的同步原语:根据场景选择互斥锁、通道或原子操作
原代码中的无限循环 for {} 也应该避免,应该使用 sync.WaitGroup 或通道来正确等待goroutine完成。


