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

3 回复

此外,您还需要注意 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切片,这会导致:

  1. 数据丢失 - 某些goroutine的append操作被覆盖
  2. 切片长度不一致 - 出现您看到的"启动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语言中处理并发访问的标准模式。

回到顶部