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())
}

image


更多关于Golang中什么原因会导致这个竞态条件的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

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()只是让问题更容易暴露,但并非必要条件。即使注释掉它,竞态条件依然存在,原因如下:

  1. 共享内存访问未同步:所有goroutine并发读取和修改counter,没有使用互斥锁或原子操作来保证顺序性。
  2. 非原子操作temp := countertemp++counter = temp这三步操作不是原子的,goroutine可能在任意步骤被调度器切换。
  3. 编译器/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.Mutexsync/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()仅是一种触发手段,并非根本原因。

回到顶部