Golang中Race Condition问题的分析与复现(附play.golang.org演示)
Golang中Race Condition问题的分析与复现(附play.golang.org演示) Todd McCleod 在一节名为“竞态条件”的课程中,使用了这段代码: https://play.golang.org/p/FYGoflKQej 来说明竞态条件不是好的代码。竞态条件会导致不可预测的结果。 我想借助这个主题,按照你们在论坛上教我的方式,逐步理解这段代码以及他所说的内容。 什么是“runtime”,有没有办法让我自己弄清楚? 什么是“sync”,有没有办法让我自己弄清楚?
hollowaykeanho: 它是否独立执行而不影响运行器?
有趣的想法
更多关于Golang中Race Condition问题的分析与复现(附play.golang.org演示)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
以下是一个有趣的定义。
同步 基本上意味着你一次只能执行一件事。异步 意味着你可以同时执行多件事,并且不必等待当前任务完成就可以继续执行下一个任务。
hollowaykeanho:
哇……冷静一下。喝杯冰柠檬汁。用简单的英语就行。没那么复杂。😅
哈哈。你说得对!但这就是我从谷歌得到的答案类型。我很少能读完第一句或第二句。
现在有点跑题了。刚开始学习如何在手机上浏览这个网站。
好的,我又找到了。
hollowaykeanho: 同步还是异步?
第一个外来词的意思是“同时发生”……不是吗?如果我努力回忆遥远的拉丁语课程,下一个词的意思应该相反。
hollowaykeanho: “我”与我的“手表”同步是在什么时候?
是我戴上它的时候吗?或者可能是我使用它的时候?我有一块智能手表,那是我启动健身应用的时候吗?对于老式手表,是我设置时间和日期的时候吗?
异步我就完全没头绪了。
好的,我理解您希望将HTML内容转换为Markdown格式,并遵循严格的规则:准确翻译英文文本、保持代码原样并用指定语言代码块包裹、转换图片链接、移除用户信息、只输出最终结果。
以下是根据您的要求转换后的Markdown文档:
嗯,我以为您希望我关闭所有主题。所以我随机选择了一个解决方案。感谢您的回答。 我大致理解了您的回答。
为了支持第一种方式,Go提供了
channel数据类型来进行消息传递。为了支持第二种方式,Go提供了
sync包来管理同步。
这一点,我需要进一步研究。
计算·电信
指一种计算机控制时序协议,其中特定操作在收到前一个操作已完成的指示(信号)后开始。
哇……冷静一下。喝杯冰柠檬汁。用简单的英语解释就行。没那么复杂。 😅
什么类型的事情?编程方面吗?如果是的话,我不知道。
在日常生活中?例如,说说“我”和我的“手表”,它们是如何同步或异步工作的?
“我”什么时候与我的“手表”同步?
用秒表。
很好。你开始理解了。 😄。
那么,我可以说裁判(包括主裁判)根据一个时间同步赛跑者,而这个时间是在独立运行且不影响赛跑者的吗?
有没有办法让我自己弄明白?
很高兴看到你主动学习更多知识。😁 既然如此,我只为你分解一些更小的问题,供你探索发现。
竞态条件
什么是“运行时”
什么是“同步”
首先,我们需要理解竞态条件,这里有一些很好的问题:
- 为什么需要异步地处理事情?
- 有哪些类型的异步活动可供我们使用?
- 我们日常生活中是否有任何异步发生的事情?(提供一些例子)
- 在学校的户外竞赛中,一个领导者如何带领2个或更多的团队成员?
- 假设在一场有8名选手的100码短跑比赛中,我们如何坚定地确定谁是最终的获胜者?
这些应该是你探索之旅的一个良好开端。
hollowaykeanho:
是的,但是这两个实体(同时发生)会相互交互吗?
异步是同步的反义词,其中两个或更多实体同时发生但彼此不交互。
有趣。我有点明白了。
hollowaykeanho:
2 entity
是指两行代码或两个代码项吗?
hollowaykeanho:
就像,你不会每秒都手动去转动你的手表齿轮,或者去“滴答”你的手机处理器(就像我们心脏的心跳),对吧?
非常有趣。
那么?我执行一个动作(设置手表),这个动作导致齿轮运转。我执行动作,这个动作相对于手表实际运行来说是异步的。
秒表的例子帮助我理解了。
我们可以看看这个定义吗? 异步 指一种计算机控制时序协议,其中特定操作在收到指示(信号)表明前一个操作已完成时开始。(Google)
我想我明白了。 哦!我多么希望我有更多时间
第一个外来词的意思是同时发生……不是吗?
是的,但是这两个实体(同时发生)会相互交互吗?
异步是同步的反义词,指的是两个或多个实体同时发生但彼此不交互。
那么“我”什么时候与我的“手表”同步呢?
是我戴上它的时候吗?或者可能是我使用它的时候?我有一块智能手表,是我启动健身应用的时候吗?或者对于传统手表,是我设置时间和日期的时候吗?
所以当你戴上它、使用它、启动应用、设置日期时,另一个实体(时间、手表和应用)是否在工作并不重要,对吗?就像你不会手动每秒转动表内的齿轮,或者让你的手机处理器跳动(像我们心脏的心跳)一样,对吗?
因此,我可以说,在你和手表之间,你们本质上是异步工作的,因为你有一颗让你活着的心脏,而手表有自己的电池来驱动齿轮。
附言:我将范围缩小到“我们-手表”以专注于主题。
hollowaykeanho: 在我们的例子中,“我们”作为人类是一个实体,而“手表”是另一个。
明白了。谢谢!
顺便说一句,我很喜欢这个网站的布局。
hollowaykeanho: 所以这两个词都源于“同步”,意思是在给定时间内两个或更多实体之间的交互。
是的。
hollowaykeanho: 这是一个同步行为的例子。手表和你就是两个实体。然而,你是在某个时间点,根据你想要的时间与手表进行同步。
有趣。
hollowaykeanho:
你并不会每秒都手动去转动你手表的齿轮,或者去“滴答”你的手机处理器(就像我们心脏的心跳一样),对吧?
对的。
hollowaykeanho: 你(人)和手表是两个实体,但彼此之间并没有互动。
我明白了。
hollowaykeanho: 那么,在继续深入之前,我们是否已经清楚两个实体之间的同步(同步和异步)了?
是的 🙂
顺便说一句,我一直在研究“runtime”和“sync”,并开始有所理解,但如果能用通俗的语言解释一下,我将不胜感激。
是指两行代码或两个代码项吗?
目前没有代码。实体指的是主体。在我们的例子中,“我们”作为人类是一个实体,“手表”是另一个实体。
同步 基本上意味着你一次只能执行一件事。异步 意味着你可以同时执行多件事,并且不必先完成当前正在执行的事情就可以继续执行下一件。
所以这两个词都源自“同步”,意思是在给定时间内两个或多个实体之间的交互。
那么?我执行一个动作(设置手表)导致齿轮转动。
这是一个同步动作的例子。手表和你是两个实体。然而,你是在与手表就你想要的时间进行同步。
秒表的例子帮助我理解。
你不需要每秒手动转动你的手表齿轮或手动触发你的手机处理器(就像我们心脏的心跳一样),对吧?
这些是异步的。当你不读取/设置手表时,它仍然会滴答作响,以便自行“计数”时间(齿轮通过电池供电旋转)。你和手表是两个实体,但彼此之间没有交互。
类似地,秒表只在“开始”和“停止”时与你同步。在等待“停止”时,它有自己的电池来跟踪时间,因此,状态变为异步。
到目前为止,在继续深入之前,我们是否清楚两个实体之间的同步(同步和异步)?
hollowaykeanho:
异步地?
我需要查一下这个词 😄 尽管根据我遥远的拉丁语学习经历,我大概能猜出它的意思。
计算·电信
指一种计算机控制时序协议,其中特定操作在收到前一个操作已完成的指示(信号)后开始。
好的,我想我明白了。
所以看起来你是想让我回答这些问题。
hollowaykeanho:
为什么事情要异步进行
什么样的事情?编程方面吗?如果是的话,我不知道。
hollowaykeanho:
我们可以使用哪些类型的异步活动?
你是指在 Go 语言中吗?我想我才刚刚入门。
hollowaykeanho:
我们日常生活中有什么事情是异步发生的吗?(提供一些例子)
让我想想。
不同时存在或发生。
你指的是一个过程吗?比如阅读,我们一次读一个词?或者吃饭,我们一次咀嚼一口?
hollowaykeanho:
在学校的户外竞赛中,领导者如何带领 2 名或更多团队成员
嗯。看起来他会在鼓舞士气时同时和整个团队讲话,但在个别指导时会单独沟通。
hollowaykeanho:
假设在有 8 名竞争者的 100 码短跑比赛中,我们如何坚定地确定谁是最终获胜者
用秒表。
cherilexvold1974:
我一直在研究“runtime”和“sync”,并开始理解了
我建议暂时把它放一放。休息一下,尝试先关闭其他“标记为已解决”的话题。你堆积的事情越多,负担和干扰就越大。一次只专注于一件事,最终事情会按时且有效地完成。🙃
cherilexvold1974:
hollowaykeanho:
到目前为止,在继续深入之前,我们是否清楚两个实体(同步和异步)之间的同步?
是的 🙂
现在,假设你戴着一块没有屏幕但会说话的智能手表。你设置它每15分钟报时一次,以便与你同步。因此,同步动作每15分钟发生一次。
出于某种原因,你戴着两块这样的手表,一块说英语,一块说非英语。两块表完全同步,都是每15分钟报时一次。
作为听众,当第一个15分钟到来时,会发生什么?当它们同时用不同的语言报时,你能听清楚吗?
不确定这如何成为一个解决方案,所以我将编辑它以匹配问题:
当两块手表同时试图用不同的语言报时,由于听众一次只能听一个,两块手表都在争夺听众的注意力。这就是竞态条件。
为了解决这个竞态问题,同时保留两块手表(我肯定是疯了),主人必须明确控制两块手表的报时时间。有很多方法可以做到这一点,但值得注意的有两种:
- 让手表的报时信息被推送到一个应用程序,就像推送通知一样。然后让通知器来报时。
- 设置一块手表在15分/45分报时;另一块在0分/30分报时。
第一种方式是异步消息传递。现在所有4个实体(包括应用程序)都可以自由地相互交互。这种现象在我们身边随处可见:从厨房烹饪食物到送到你的餐桌上。
第二种是同步解决方案,其中的线程被有目的地调整以避免相互冲突。这种现象就像规划你的日历,你不会希望两个事件在同一时间冲突。
为了支持第一种方式,Go提供了 channel 数据类型来进行消息传递。
为了支持第二种方式,Go提供了 sync 包来管理同步。
在Go语言中,竞态条件(Race Condition)是指多个goroutine在没有正确同步的情况下并发访问共享数据,导致程序行为不可预测。Todd McCleod提供的代码示例展示了典型的竞态条件问题。
代码分析
原示例代码(简化版):
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
counter := 0
const gs = 100
var wg sync.WaitGroup
wg.Add(gs)
for i := 0; i < gs; i++ {
go func() {
v := counter
runtime.Gosched()
v++
counter = v
wg.Done()
}()
}
wg.Wait()
fmt.Println("counter:", counter)
}
runtime包解析
runtime包提供了与Go运行时系统交互的功能。在竞态条件分析中,关键函数是:
// runtime.Gosched() 让出当前goroutine的执行权
runtime.Gosched()
// 查看逻辑CPU数量
fmt.Println("CPU cores:", runtime.NumCPU())
// 设置最大并发执行的goroutine数量
runtime.GOMAXPROCS(1)
// 查看当前goroutine数量
fmt.Println("Goroutines:", runtime.NumGoroutine())
runtime.Gosched()主动让出CPU时间片,使其他goroutine有机会执行,这有助于暴露竞态条件。
sync包解析
sync包提供了基本的同步原语。解决竞态条件的关键组件:
// 使用互斥锁解决竞态条件
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()
// 使用原子操作解决竞态条件
import "sync/atomic"
var counter int64
atomic.AddInt64(&counter, 1)
// WaitGroup等待goroutine完成
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// 工作代码
}()
wg.Wait()
竞态条件复现代码
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// 设置使用所有CPU核心
runtime.GOMAXPROCS(runtime.NumCPU())
// 竞态条件演示
fmt.Println("=== Race Condition Demo ===")
demoRaceCondition()
// 使用互斥锁解决
fmt.Println("\n=== Mutex Solution ===")
demoMutexSolution()
// 使用原子操作解决
fmt.Println("\n=== Atomic Solution ===")
demoAtomicSolution()
}
func demoRaceCondition() {
counter := 0
const gs = 100
var wg sync.WaitGroup
wg.Add(gs)
for i := 0; i < gs; i++ {
go func() {
v := counter
runtime.Gosched() // 增加竞态条件发生概率
v++
counter = v
wg.Done()
}()
}
wg.Wait()
fmt.Printf("Expected: 100, Got: %d\n", counter)
}
func demoMutexSolution() {
counter := 0
const gs = 100
var wg sync.WaitGroup
var mu sync.Mutex
wg.Add(gs)
for i := 0; i < gs; i++ {
go func() {
mu.Lock()
v := counter
runtime.Gosched()
v++
counter = v
mu.Unlock()
wg.Done()
}()
}
wg.Wait()
fmt.Printf("Expected: 100, Got: %d\n", counter)
}
func demoAtomicSolution() {
var counter int64
const gs = 100
var wg sync.WaitGroup
wg.Add(gs)
for i := 0; i < gs; i++ {
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Printf("Expected: 100, Got: %d\n", counter)
}
检测竞态条件
使用Go内置的竞态检测器:
go run -race main.go
go build -race main.go
竞态检测器会报告类似这样的错误:
WARNING: DATA RACE
Read at 0x00c00001c0a8 by goroutine 7:
main.demoRaceCondition.func1()
/path/to/main.go:30 +0x47
Previous write at 0x00c00001c0a8 by goroutine 6:
main.demoRaceCondition.func1()
/path/to/main.go:32 +0x63
理解runtime和sync包的方法
- 查看官方文档:
go doc runtime
go doc sync
go doc sync/atomic
- 查看源码:
# 查看runtime包源码
cd $(go env GOROOT)/src/runtime
# 查看sync包源码
cd $(go env GOROOT)/src/sync
- 编写测试代码:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func exploreRuntime() {
fmt.Println("GOROOT:", runtime.GOROOT())
fmt.Println("GOOS:", runtime.GOOS)
fmt.Println("Compiler:", runtime.Compiler)
// 内存统计
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)
}
func exploreSync() {
// Once示例
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
for i := 0; i < 5; i++ {
go func() {
once.Do(onceBody)
}()
}
time.Sleep(time.Second)
}
竞态条件的核心问题是并发访问共享状态缺乏同步。runtime包提供运行时控制,sync包提供同步原语。正确使用这些工具可以避免竞态条件,确保并发程序的正确性。

