关于 GO 语言字母与数字交叉打印的问题

128 天前
 Betsy
package main

import (
    "fmt"
    "sync"
)

func main() {
    ch := make(chan bool, 1)
    defer close(ch)

    wg := sync.WaitGroup{}
    wg.Add(2)

    go func() {
        ch <- true

        defer wg.Done()
        var nums []int = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

        for _, num := range nums {
            <-ch
            fmt.Print(num, " ")
            ch <- true
        }
    }()

    go func() {
        defer wg.Done()
        var letters []byte = []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

        for _, letter := range letters {
            <-ch
            fmt.Print(string(letter), " ")
            ch <- true
        }
    }()

    wg.Wait()
}

上述代码会概率性的打印下述两种结果。为什么会出现这种现象?

a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 
1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i j 

相比较上述代码只是将 第 1 个 goroutine 第一行的 ch <- true 移动到了第 2 个 goroutine 中

package main

import (
    "fmt"
    "sync"
)

func main() {
    ch := make(chan bool, 1)
    defer close(ch)

    wg := sync.WaitGroup{}
    wg.Add(2)

    go func() {
        defer wg.Done()
        var nums []int = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

        for _, num := range nums {
            <-ch
            fmt.Print(num, " ")
            ch <- true
        }
    }()

    go func() {
        ch <- true

        defer wg.Done()
        var letters []byte = []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

        for _, letter := range letters {
            <-ch
            fmt.Print(string(letter), " ")
            ch <- true
        }
    }()

    wg.Wait()
}

上述代码会概率性的打印出下述几种结果。为什么会出现这种现象?

a b c d e f g h i j 1 2 3 4 5 6 7 8 9 
a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j
a b 1 c 2 d 3 e 4 f 5 g 6 h 7 i 8 j 9

如果执行次数比较少,可能不会出现这个现象。但是如果执行上百次,就会出现。

for i in {1..100}; do go run main.go; done

如果我想稳定的先打印数字,再打印字母,应该如何修改?

2221 次点击
所在节点    Go 编程语言
11 条回复
nagisaushio
128 天前
用两个 channel
kirara2024
128 天前
package main

import (
"fmt"
"sync"
)

func main() {
numDone := make(chan bool)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
defer close(numDone)
var nums []int = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

for _, num := range nums {
fmt.Print(num, " ")
}
fmt.Println()
}()

go func() {
defer wg.Done()
<-numDone
var letters []byte = []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

for _, letter := range letters {
fmt.Print(string(letter), " ")
}
fmt.Println()
}()

wg.Wait()
}
dV9zZM1wROuBT16X
127 天前
goroutine 的运行不是按顺序的,一般是 FILO ,只有饥饿了才会 FIFO ,你还要考虑下其他 M 空闲时窃取 G
rekulas
127 天前
这才是符合预期的,你用两个协程打印,就算用了 chan 又如何,你不能保证一个协程提交了数据马上又自己抢占到,这又不是一个原子操作,而且在不同硬件,环境的机器上估计结果也有差距..
要稳定的两个协程打印数字最稳的还是一个加锁,确认打印完成了另一个才拿到锁就行了,或者用协程池的模式顺序执行协程队列
dallaslu
127 天前
这是在模拟《功夫》中的拍卖过程吗?按套路来,一人一句,不能喊乱了。顺着 @nagisaushio #1 的思路,用 两个 channel 来捋一捋。

范厨师:你别说话,啊!两千,两千五,三千,三千五,四千,四千五,五千!

```go
chN := make(chan bool, 1)
chC := make(chan bool, 1)
defer close(chN)
defer close(chC)

wg := sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()
var nums []int = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

for _, num := range nums {
<-chN
fmt.Print(num, " ")
chC <- true
}
}()

go func() {
defer wg.Done()
var letters []byte = []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'}

for _, letter := range letters {
<-chC
fmt.Print(string(letter), " ")
chN <- true
}
}()

chN <- true

wg.Wait()
```
wwhontheway
127 天前
不能预设 goroutine 的运行时机和顺序
rower
127 天前
我建议先看看正确的答案怎么写的
pkoukk
127 天前
用两个 channel
6# 说的很对,不要对 goroutine 的运行时间和顺序有任何预设
如果他们有顺序,那你开 goroutine 是图个啥?
Betsy
127 天前
@rower 我知道怎么写的话,就不会来这问了 😭
bronyakaka
127 天前
假设第二个 go 先执行,执行到<-ch 会被阻塞,卡住,因为此时通道没元素;
这个时候第一个 go 就肯定执行了,一旦执行了 ch <- true ,第 2 个 go 就可以完成<-ch 继续执行了,当然也有可能由于比较慢导致第一个 go 继续执行,但此时会被<-ch 阻塞,这种情况下肯定先打印字母;

假设第一个 go 先执行,一路打印了数字走到 ch <- true ,此时第二个 go 执行了,畅通无阻可以走到打印字母的地方。这种情况就是交叉了,先数字再字母
rower
127 天前
我建议谷歌搜下标题,或者问 AI ,这个是常见的问题,有答案的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1134763

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX