Golang中Goroutine问题的解决方案探讨
Golang中Goroutine问题的解决方案探讨 我正在从《Go实战》这本书中学习goroutine,并运行以下代码:
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int
wg sync.WaitGroup
)
func main(){
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println("final counter: ", counter)
}
func incCounter(input int){
defer wg.Done()
for count := 0; count < 2; count++{
value := counter
//do werld thing
runtime.Gosched()
value ++
counter = value
}
}
由于这两个goroutine在更新包级变量counter时会相互覆盖彼此的工作,所以最终的counter值应该总是2。我尝试运行后发现,大多数时候得到的结果是2,但有时会得到3或4。这是什么原因呢?
(base) zwang-mac:cmd1 zwang$ go run race.go
final counter: 2
(base) zwang-mac:cmd1 zwang$ go run race.go
final counter: 2
(base) zwang-mac:cmd1 zwang$ go run race.go
final counter: 3
(base) zwang-mac:cmd1 zwang$ go run race.go
final counter: 2
(base) zwang-mac:cmd1 zwang$
更多关于Golang中Goroutine问题的解决方案探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢您如此详细的解释!作为后续跟进,您认为我可以使用什么可能的解决方案来解决这个数据竞争问题?一般来说,编写多进程的goroutine有哪些最佳实践?
谢谢
更多关于Golang中Goroutine问题的解决方案探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
以下是一个可能的修复示例。它使用互斥锁来确保每次只有一个go例程递增计数器。
func main() {
fmt.Println("hello world")
}
当你运行它时,计数器将始终为4。
一句话:尽可能详细地规划,直至数据访问层面。
当你能够随时回答以下模式时,说明你的规划已步入正轨:
谁在何时通过何种方式执行什么任务
// 代码示例保留原文
func main() {
fmt.Println("hello world")
}
一句话:尽可能详细地规划,直至数据访问层面。
当你能随时回答以下模式时,说明你的规划已步入正轨:
谁在何时通过何种方式执行什么任务
为了支持他的观点😜,以下是他过往的一些解释:
如何规划
- 同步示例问题代码 - hollowaykeanho的第3条回复 - 关于死锁的更多信息
- 出现难以理解的死锁 - hollowaykeanho的第4条回复 - 使用伪代码进行规划
这是一个典型的数据竞争(data race)问题。在并发编程中,当多个goroutine同时访问共享变量且至少有一个进行写入操作时,如果没有适当的同步机制,就会出现不可预测的结果。
在你的代码中,问题出现在incCounter函数的这个关键部分:
value := counter // 读取counter值
runtime.Gosched() // 主动让出CPU时间片
value++ // 增加本地副本
counter = value // 写回counter
当两个goroutine并发执行时,可能出现以下竞态条件:
- Goroutine 1读取counter值为0
- Goroutine 2读取counter值为0
- Goroutine 1增加value为1,写回counter=1
- Goroutine 2增加value为1,写回counter=1(覆盖了Goroutine 1的结果)
- 最终counter只增加了1次而不是2次
但有时也会出现counter=3或4的情况,这是因为在某些执行顺序下,goroutine的读取和写入操作交叉进行,导致某些增加操作被重复计算。
要解决这个问题,需要使用同步原语来保护对共享变量的访问。以下是几种解决方案:
方案1:使用互斥锁(Mutex)
package main
import (
"fmt"
"sync"
)
var (
counter int
wg sync.WaitGroup
mutex sync.Mutex
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println("final counter: ", counter)
}
func incCounter(input int) {
defer wg.Done()
for count := 0; count < 2; count++ {
mutex.Lock()
value := counter
// 这里不再需要runtime.Gosched()
value++
counter = value
mutex.Unlock()
}
}
方案2:使用原子操作
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var (
counter int32
wg sync.WaitGroup
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println("final counter: ", counter)
}
func incCounter(input int) {
defer wg.Done()
for count := 0; count < 2; count++ {
atomic.AddInt32(&counter, 1)
}
}
方案3:使用通道(Channel)
package main
import (
"fmt"
"sync"
)
var (
counter int
wg sync.WaitGroup
)
func main() {
wg.Add(2)
// 创建用于同步的通道
ch := make(chan int, 1)
ch <- counter // 初始化通道值
go incCounter(1, ch)
go incCounter(2, ch)
wg.Wait()
// 读取最终结果
final := <-ch
fmt.Println("final counter: ", final)
}
func incCounter(input int, ch chan int) {
defer wg.Done()
for count := 0; count < 2; count++ {
current := <-ch
current++
ch <- current
}
}
使用go run -race race.go可以检测数据竞争问题,Go的竞态检测器会明确报告存在数据竞争的位置。

