Golang中这是否属于竞态条件?

Golang中这是否属于竞态条件? 大约一小时前,我被要求创建一个竞态条件并用互斥锁解决它。

我编写了以下代码来描绘一个竞态条件:

func main() {
	var data int
	go func() {
		data++
	}()
	if data == 0 {
		fmt.Println("The value of data is", data)
	} else {
		fmt.Println("The value of data is", data)
	}
}

然后我添加了互斥锁来解决竞态条件,如下所示:

func main() {
	var data int
	var mu sync.Mutex
	go func() {
		mu.Lock()
		data++
		mu.Unlock()
	}()
	mu.Lock()
	if data == 0 {
		fmt.Println("The value of data is", data)
	} else {
		fmt.Println("The value of data is", data)
	}
	mu.Unlock()
}

面试官评论说第一段代码并没有描绘出竞态条件。如果我说错了请纠正我,但我认为它确实描绘了,你能帮忙解释一下吗?


更多关于Golang中这是否属于竞态条件?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

感谢,因为你,我学到了一个新的标志 🙂

更多关于Golang中这是否属于竞态条件?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。出于某种原因,面试官认为这不是一个竞态条件。

这确实是一个数据竞争。你可以通过使用 -race 标志运行程序来确认这一点。

是的,确实如此。我想用一个更清晰的例子来演示这一点,这个例子包含多个非主函数的 goroutine。我也用 -race 参数运行了他的代码,并且检测到了数据竞争。

// 代码示例应放置于此

我认为Go将代码标记为存在竞态条件,是因为你从一个goroutine中访问了一个全局变量。当两个线程同时访问一个共享变量时,就会发生竞态条件

如果你在运行程序时不使用竞态检测标志,程序会正常运行(因为goroutine没有机会被执行)。

实际上,这正是 @MukulLatiyan 的代码所做的事情。这里有两个执行线程(新启动的 goroutine 和主代码线程,在此上下文中主线程也可被视为一个 goroutine),并且其中一个执行了写操作。

package main

import (
    "fmt"
    "time"
)

func main() {
    var data int
    go func() {
        data++
    }()
    time.Sleep(100 * time.Millisecond)
    if data == 0 {
        fmt.Printf("the value is %v.\n", data)
    }
}

你遇到了一个竞态条件。数据竞争的发生需要满足两个或更多 goroutine 同时访问同一个变量,并且其中至少有一个访问是写操作,就像下面这样:

package main

var balance int 

func Deposit(amount int) { 
	balance = balance + amount 
} 

func Balance() int { 
	return balance 	
}

func main() {
	go Deposit(200)
	go Deposit(100)
	
}

要解决这个问题:

package bank

import "sync"

var (
    mu      sync.Mutex
    balance int
)

func Deposit(amount int) {
    mu.Lock()
    balance = balance + amount

    mu.Unlock()
}

func Balance() int {
    mu.Lock()

    defer mu.Unlock()

    return balance
}

你可以在以下仓库中找到更多示例:https://github.com/adonovan/gopl.io/tree/master/ch9 http://www.gopl.io/

是的,第一段代码确实存在竞态条件。竞态条件发生在多个goroutine并发访问共享变量且至少有一个是写入操作时,而代码的执行结果依赖于goroutine的执行顺序。

在你的例子中:

func main() {
    var data int
    go func() {
        data++  // 写入操作
    }()
    if data == 0 {  // 读取操作
        fmt.Println("The value of data is", data)
    } else {
        fmt.Println("The value of data is", data)
    }
}

存在三种可能的执行顺序:

  1. goroutine先执行data++先执行,输出显示The value of data is 1
  2. 读取先执行if data == 0先执行,输出显示The value of data is 0
  3. 读取发生在写入中间data++不是原子操作,可能读取到中间状态

使用互斥锁的解决方案是正确的:

func main() {
    var data int
    var mu sync.Mutex
    
    go func() {
        mu.Lock()
        data++
        mu.Unlock()
    }()
    
    mu.Lock()
    if data == 0 {
        fmt.Println("The value of data is", data)
    } else {
        fmt.Println("The value of data is", data)
    }
    mu.Unlock()
}

这个解决方案确保了data++和读取操作是互斥的,消除了竞态条件。输出结果现在是确定性的,不会依赖于goroutine的执行顺序。

回到顶部