Golang中意外数量的启动例程问题探讨
Golang中意外数量的启动例程问题探讨 我获取用户输入的数字,然后运行与输入数字相等数量的goroutine。这看起来没问题,但有时我会得到意外的结果,当启动的goroutine数量少于输入的数字时。
fmt.Println("Enter number from 2 to 13")
var num int
//accepts only 2-13
for {
in := bufio.NewScanner(os.Stdin)
in.Scan()
num, _ = strconv.Atoi(in.Text())
if num >= minRoutinesNumber && num <= maxRoutinesNumber {
break
}
}
fmt.Println("num =", num)
routines := []Player{}
for i := 1; i <= num; i++ {
go func(i int) {
//fmt.Println("goroutine index", i)
routines = append(routines, Player{name: "", id: i})
}(i)
}
time.Sleep(1 * time.Second)
fmt.Println("players rotines lengts", len(routines))
for _, r := range routines {
fmt.Println("player.id:", r.id)
}
有时我会得到意外的goroutine数量,为什么?
例如,上次我输入5,但得到了players rotines lengts 2 - 这有问题…
andy@sol:~/go/src/cities$ go run main.go
Enter number from 2 to 13
7
num = 7
players rotines lengts 7
player.id: 5
player.id: 6
player.id: 7
player.id: 2
player.id: 1
player.id: 3
player.id: 4
andy@sol:~/go/src/cities$ go run main.go
Enter number from 2 to 13
8
num = 8
players rotines lengts 8
player.id: 4
player.id: 1
player.id: 2
player.id: 3
player.id: 6
player.id: 5
player.id: 7
player.id: 8
andy@sol:~/go/src/cities$ go run main.go
Enter number from 2 to 13
5
num = 5
players rotines lengts 2 // !!!!!!!!!
player.id: 2
player.id: 1
andy@sol:~/go/src/cities$
更多关于Golang中意外数量的启动例程问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
此外,您还需要注意 append 操作不是线程安全的。
更多关于Golang中意外数量的启动例程问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
引用 cinematik 的话:
time.Sleep(1 * time.Second)
这不是正确的同步方式,建议改用 sync.WaitGroup。
您遇到的问题是由于Go语言中并发访问共享数据时缺乏同步机制导致的。当多个goroutine同时向同一个切片执行append操作时,会发生数据竞争,导致意外的结果。
问题出现在这里:
routines := []Player{}
for i := 1; i <= num; i++ {
go func(i int) {
routines = append(routines, Player{name: "", id: i})
}(i)
}
多个goroutine并发修改routines切片,这会导致:
- 数据丢失 - 某些goroutine的append操作被覆盖
- 切片长度不一致 - 出现您看到的"启动5个goroutine但只得到2个结果"
解决方案是使用同步机制。以下是几种正确的实现方式:
方案1:使用sync.WaitGroup和互斥锁
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"sync"
)
type Player struct {
name string
id int
}
func main() {
const minRoutinesNumber = 2
const maxRoutinesNumber = 13
fmt.Println("Enter number from 2 to 13")
var num int
for {
in := bufio.NewScanner(os.Stdin)
in.Scan()
num, _ = strconv.Atoi(in.Text())
if num >= minRoutinesNumber && num <= maxRoutinesNumber {
break
}
}
fmt.Println("num =", num)
routines := []Player{}
var mu sync.Mutex
var wg sync.WaitGroup
for i := 1; i <= num; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mu.Lock()
routines = append(routines, Player{name: "", id: i})
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println("players routines length", len(routines))
for _, r := range routines {
fmt.Println("player.id:", r.id)
}
}
方案2:使用通道收集结果
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"sync"
)
type Player struct {
name string
id int
}
func main() {
const minRoutinesNumber = 2
const maxRoutinesNumber = 13
fmt.Println("Enter number from 2 to 13")
var num int
for {
in := bufio.NewScanner(os.Stdin)
in.Scan()
num, _ = strconv.Atoi(in.Text())
if num >= minRoutinesNumber && num <= maxRoutinesNumber {
break
}
}
fmt.Println("num =", num)
results := make(chan Player, num)
var wg sync.WaitGroup
for i := 1; i <= num; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
results <- Player{name: "", id: i}
}(i)
}
go func() {
wg.Wait()
close(results)
}()
routines := []Player{}
for player := range results {
routines = append(routines, player)
}
fmt.Println("players routines length", len(routines))
for _, r := range routines {
fmt.Println("player.id:", r.id)
}
}
方案3:使用原子操作和固定大小数组
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"sync/atomic"
)
type Player struct {
name string
id int
}
func main() {
const minRoutinesNumber = 2
const maxRoutinesNumber = 13
fmt.Println("Enter number from 2 to 13")
var num int
for {
in := bufio.NewScanner(os.Stdin)
in.Scan()
num, _ = strconv.Atoi(in.Text())
if num >= minRoutinesNumber && num <= maxRoutinesNumber {
break
}
}
fmt.Println("num =", num)
routines := make([]Player, num)
var index int32 = -1
for i := 1; i <= num; i++ {
go func(i int) {
pos := atomic.AddInt32(&index, 1)
routines[pos] = Player{name: "", id: i}
}(i)
}
// 等待所有goroutine完成
time.Sleep(1 * time.Second)
fmt.Println("players routines length", len(routines))
for _, r := range routines {
fmt.Println("player.id:", r.id)
}
}
推荐使用方案1或方案2,它们提供了更好的同步保证。方案1使用互斥锁保护共享数据,方案2使用通道进行goroutine间通信,都是Go语言中处理并发访问的标准模式。

