Golang中什么原因会导致这个竞态条件
Golang中什么原因会导致这个竞态条件
我正在Udemy上学习一门课程,其中一个演示竞态条件的练习使用了runtime.Gosched()方法。然而,当我注释掉runtime.Gosched()这一行(如下所示),并运行go run -race fileName.go时,仍然会出现竞态条件。讲师建议我来这里提问。希望有人能帮我解惑——我确实还是个Go语言新手。😅
谢谢。
func simulateRaceCondition() {
/******
* Ex 3: Using goroutines, create an incrementer program
* read the incrementer value, store it in a new variable, yield the processor with runtime.Gosched(), increment the new variable
* show that a race condition is raised
******/
counter := 0
goRoutines := 50
var wg sync.WaitGroup
wg.Add(goRoutines)
for ; goRoutines != 0; goRoutines-- {
go func() {
temp := counter
// runtime.Gosched()
temp++
counter = temp
fmt.Println("counter", counter)
wg.Done()
}()
}
wg.Wait()
fmt.Println(counter)
fmt.Println("Done")
fmt.Println(runtime.NumGoroutine())
}

更多关于Golang中什么原因会导致这个竞态条件的实战教程也可以访问 https://www.itying.com/category-94-b0.html
WaitGroup 仅确保所有 goroutine 在调用 Done 后继续执行前完成。它不会做任何其他事情来确保内存访问是同步的。
func main() {
fmt.Println("hello world")
}
更多关于Golang中什么原因会导致这个竞态条件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
嘿,肖恩,
感谢你的详细解答。我想我的问题是,基于提供的代码,是什么可能导致这种情况(就假设这是整个应用程序)?我原本的理解是,如果我正确使用了 WaitGroups,这应该能避免竞态条件的场景;这种理解不正确吗?我需要指出,最终的计数器值是正确的,所以看起来并没有导致功能性问题,但触发了竞态条件这一事实让我感到困扰。
嗨,Branden,
调用 runtime.Gosched() 会挂起当前的 goroutine 以允许另一个运行。如果你的代码存在竞态条件,无论是否调用该函数,你都会遇到它*。竞态条件是指你对一个变量有两次或更多次并发访问,并且其中至少有一次是写入操作。你的竞态条件在于你如何在没有同步的情况下,从多个 goroutine 并发地更改你的计数器变量。
- 假设 GOMAXPROCS 大于 1 并且你的计算机拥有超过 1 个硬件线程。我认为是这样。我对 x86 内存模型有尚可的理解,但对 Go 的并不特别了解。
runtime.Gosched()
竞态条件的根本原因是多个goroutine同时读写共享变量counter,而runtime.Gosched()只是让问题更容易暴露,但并非必要条件。即使注释掉它,竞态条件依然存在,原因如下:
- 共享内存访问未同步:所有goroutine并发读取和修改
counter,没有使用互斥锁或原子操作来保证顺序性。 - 非原子操作:
temp := counter、temp++、counter = temp这三步操作不是原子的,goroutine可能在任意步骤被调度器切换。 - 编译器/CPU优化:即使没有显式调用
runtime.Gosched(),Go调度器仍可能在任何时间点切换goroutine,或者CPU指令重排也可能导致意外结果。
以下示例演示了即使没有runtime.Gosched(),竞态条件依然会被检测到:
package main
import (
"fmt"
"sync"
)
func main() {
counter := 0
const goroutines = 50
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
temp := counter // 竞态读
temp++
counter = temp // 竞态写
wg.Done()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter) // 结果通常小于50
}
运行go run -race main.go会报告类似:
WARNING: DATA RACE
Read at 0x00c0000b4008 by goroutine 8:
main.main.func1()
main.go:17 +0x47
Previous write at 0x00c0000b4008 by goroutine 7:
main.main.func1()
main.go:19 +0x64
修复方案:使用sync.Mutex或sync/atomic包:
// 使用互斥锁
var mu sync.Mutex
go func() {
mu.Lock()
temp := counter
temp++
counter = temp
mu.Unlock()
wg.Done()
}()
// 或使用原子操作
go func() {
atomic.AddInt32(&counter, 1)
wg.Done()
}()
总结:竞态条件是由未同步的并发内存访问导致的,runtime.Gosched()仅是一种触发手段,并非根本原因。

