Golang中检测 Data Race

Data Race概念 Data Race (数据竞争),是并发系统中最常见和最难调试的错误类型之一,当两个线程(协程)同时访问同一个变量并且至少其中一次访问是写入时,就会发生 Data Race 。Java 内存模型(JMM)定义了明确的 Happens-Before 规则和提供 Volatile 机制来初步保证程序的执行顺序,Go内存模型(GMM)同样也有相同的规则定义,此外 Golang 还提供了一种检测 Data Race 的机制,帮助开发者更好的分析并发代码的正确性。 go在1.1版本时支持data race 检测。 Data Race 和 Race Condition 的区别: 这两个概念非常容易混淆,stackoverflow上也有讨论这个问题,其实我认为这篇文章很好的区别了它们,所以我进行了翻译。 检测 Data Race 使用 -race 参数可以用来检测这种竞态: 1 2 3 4 $ go test -race mypkg // to test the package $ go run -race mysrc.go // to run the source file $ go build -race mycmd // to build the command $ go install -race mypkg // to install the package 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { c := make(chan int) m := make(map[string]int) go func() { m["a"] = 1 // 访问map冲突 c <- 1 }() m["a"] = 2 // 访问map冲突 <-c for k, v := range m { fmt.Printf("key = %v, val = %v\n", k, v) } } 示例程序中,两个 goroutine 同时读写 map 存在竞态,可能造成数据不正确的情况,但是错误难以发现,通过执行时添加 -race 选择可以检测: ...

2024-06-20 · 5 min · 1042 words · Hank

Data Race vs Race Condition

原文地址: https://blog.regehr.org/archives/490, 翻译并略作改动。 竞态条件(race Condition)是当事件的时间或顺序影响程序的正确性时发生的缺陷。一般来说,需要某种外部计时或排序非确定性来产生竞态条件;典型的例子有上下文切换、操作系统信号、多处理器上的内存操作和硬件 中断。 当程序中有两次内存访问时,就会发生数据竞争(Data Race): 目标为同一内存位置 由两个线程同时执行 不是读取操作 不是同步操作 上边这个定义来自微软研究院的 Sebastian Burckhardt。该定义的两个方面需要注意: “同时”意味着没有像锁这样的东西强制一个操作在另一个操作之前或之后发生。 “不是同步操作”是指程序可能包含特殊的内存操作,例如用于实现锁的操作,这些操作本身并不同步。 在实践中,它们两者存在相当大的重合:许多 Race Condition 是由 Data Race 引起的,并且许多 Data Race 导致 Race Condition。另一方面,两者也可以相互独立,可能产生没有 Data Race 的 Race Condition,也可能产生没有 Race Condition 的 Data Race。 让我们从一个在两个银行账户之间转移资金的简单函数开始: transfer1 (amount, account_from, account_to) { if (account_from.balance < amount) return NOPE; account_to.balance += amount; account_from.balance -= amount; return YEP; } 当然,这并不是银行真正转移资金的方式,但这个例子非常有用。我们知道,账户余额应该是非负的,并且转移之后不能凭空创造(多出)或损失(丢失)金钱。当在没有外部同步的情况下从多个线程调用时,该函数会产生 Data Race(多个线程可以同时尝试更新帐户余额)和 Race Condition(在并行上下文中它将创造或损失金钱)。 我们可以尝试这样修复它: transfer2 (amount, account_from, account_to) { atomic { bal = account_from.balance; } if (bal < amount) return NOPE; atomic { account_to.balance += amount; } atomic { account_from.balance -= amount; } return YEP; } 这里的“atomic”(原子性)是由语言运行时实现的,也许简单地通过在原子块开始时获取线程互斥体(Mutex)并在结束时释放它,也许使用某种事务(Transaction),或者也许通过禁用中断 —— 出于示例的目的,只要 atomic 块内的代码以原子方式执行就能解决竞争问题。 ...

2024-06-18 · 2 min · 235 words · Hank