• 来碗绿豆汤
    2018-09-19
    我有一个更简单的实现方式, 如下
    func main(){
        ch := make(chan struct{})
        for i:=0; i < 100; i++{
            go func(i int){
                fmt.Println(i)
                ch <- struct{}{}
            }(i)
            <-ch
        }
    }
    这样,每次循环都包装goroutine 执行结束才进入下一次循环,就可以保证顺序执行了
    展开

    作者回复: 这些go函数的真正执行谁先谁后是不可控的,所以这样做不行的。

     4
     24
  • xiao豪
    2018-09-19
    回楼上,atomic的加操作和读操作只有32位和64位整数型,所以必须要把int转为intxx。之所以这么做是因为int位数是根据系统决定的,而原子级操作要求速度尽可能的快,所以明确了整数的位数才能最大地提高性能。
    
     21
  • 枫林火山
    2019-04-01
    老师,关于顺序打印的demo40.go的优化版本,同来碗绿豆汤同学的实现
    package main

    import "fmt"

    func main() {

        var num = 10
        sign := make(chan struct{}, 1)

        for i := 0; i < num; i++ {
            go func(i int) {
                fmt.Println(i)
                sign <- struct{}{}
            }(i)
            <-sign
        }

    }
    这样写为什么不能保证同步,能不能再详细解释下呢。这个实现和您demo39.go 相比,只是合并了两处for循环,我看好多同学也有这个疑问,向您求解。
    demo40.go的实现相当于实现了每个异步线程的一个轮询loop。 上面的实现相当于单步间加了一个barrier。执行1->等待->执行2->等待。 实在没理解为什么不能保证同步
    展开

    作者回复: 我又看了一下“来碗绿豆汤”同学写的代码。我可能当时没看清楚,或者没说清楚。

    他写的这段代码单从“顺序打印数字”的要求上看是可以的。但是这样做就变成纯同步的流程了,go函数就完全没必要写了。把go函数中的代码拿出来、删掉go函数,再把通道的相关代码也删掉,岂不是更直截了当?像这样:

        for i := 0; i < num; i++ {
            fmt.Println(i)
        }

    这个题目的要求是“使得在for循环中启用的多个goroutine按照既定的顺序运行”。你也可以把它理解为“在异步的情况下顺序的打印数字”。所以,“来碗绿豆汤”同学写的代码只满足了其中一个要求,而没有让go函数们自由的异步执行。

    我的那个版本demo40.go是让各个go函数(确切地说,是它们调用的trigger函数)自行地检查所需条件,然后再在条件允许的情况下打印数字。这也叫“自旋”。这与纯同步的流程是有本质上的区别的。

    
     20
  • Askerlve
    2018-09-19
    package main

    import (
        "fmt"
        "sync/atomic"
    )

    func main() {
        var count uint32
        trigger := func(i uint32, fn func()) {
            for {
                if n := atomic.LoadUint32(&count); n == i {
                    fn()
                    atomic.AddUint32(&count, 1)
                    break
                }
            }
        }
        for i := uint32(0); i < 10; i++ {
            go func(i uint32) {
                fn := func() {
                    fmt.Println(i)
                }
                trigger(i, fn)
            }(i)
        }
        trigger(10, func() {})
    }

    测试了下,这个函数的输出不受控,并且好像永远也不会结束,有人能帮忙解释下吗,go小白~😀
    展开

    作者回复: 可以加个sleep

     2
     9
  • 嗷大猫的鱼
    2018-09-20
    老师,最近从头学习,前面一直没跟着动手,也没自己总结。这几天在整理每章的重点!

    https://github.com/wenxuwan/go36

    刚写完第二章,突然发现自己动手总结和只看差好多。我会继续保持喜欢总结!

    作者回复: 很好,加油!

    
     5
  • Askerlve
    2018-09-19
    package main

    import (
        "fmt"
        "sync/atomic"
    )

    func main() {
        var count uint32
        trigger := func(i uint32, fn func()) {
            for {
                if n := atomic.LoadUint32(&count); n == i {
                    fn()
                    atomic.AddUint32(&count, 1)
                    break
                }
            }
        }
        for i := uint32(0); i < 10; i++ {
            go func(i uint32) {
                fn := func() {
                    fmt.Println(i)
                }
                trigger(i, fn)
            }(i)
        }
        trigger(10, func() {})
    }

    这个函数的执行还是不可控诶,并且好像永远也不会结束,是因为我的go版本问题吗?
    展开

    作者回复: Win下可能会有问题,你在bif语句后边加一句time.sleep(time.Nanosecond)。github上的代码我已经更新了。

    
     5
  • 新垣结裤
    2018-09-21
    func main() {
        num := 10
        chs := [num+1]chan struct{}{}
        for i := 0; i < num+1; i++ {
            chs[i] = make(chan struct{})
        }
        for i := 0; i < num; i++ {
            go func(i int) {
                <- chs[i]
                fmt.Println(i)
                chs[i+1] <- struct{}{}
            }(i)
        }
        chs[0] <- struct{}{}
        <- chs[num]
    }
    每个goroutine执行完通过channel通知下一个goroutine,在主goroutine里控制第一个goroutine的开始,并接收最后一个goroutine结束的信号
    展开

    作者回复: 搞这么多通道有些浪费啊。另外切片不是并发安全的数据类型,最好不要这样用。

    
     4
  • 老茂
    2018-10-15
    不加sleep程序不能正常结束的情况貌似跟cpu核数有关,我是4核cpu,打印0到2每次都可以正常执行;0到3以上就会有卡主的情况,卡主时cpu达到100%,load会超过4。猜测是不是此时所有cpu都在处理count==0的for循环,没有空闲的cpu执行atomic.AddUint32(&count, 1)?

    作者回复: Go语言调度goroutine是准抢占式的,虽然会防止某个goroutine运行太久,并做换下处理。但是像简单的死循环这种有可能会换下失败,尤其是windows下,这跟操作系统的底层支持有关。不过一般情况下不用担心。

    
     3
  • Geek_3241ef
    2019-08-26
    你好,郝老师,请问这里为什么需要sleep呢,我理解的如果不加sleep,其中某个g会一直轮询count的值,当另一个g更改这个值时,那么第一个g就会判断相等才对呀。
    但实际上去掉sleep后,程序确实没有按照我理解的逻辑执行,请问这是为什么呢

    作者回复: 这主要是因为:Go 调度器在需要的时候只会对正在运行的 goroutine 发出通知,试图让它停下来。但是,它却不会也不能强行让一个 goroutine 停下来。

    所以,如果一条 for 语句过于简单的话,比如这里的 for 语句就很简单(因为里面只有一条 if 语句),那么当前的 goroutine 就可能不会去正常响应(或者说没有机会响应)Go 调度器的停止通知。

    因此,这里加一个 sleep 是为了:在任何情况下(如任何版本的 Go、任何计算平台下的 Go、任何的 CPU 核心数等),内含这条 for 语句的这些 goroutine 都能够正常地响应停止通知。

    
     2
  • 肖恩
    2019-05-08
    第一遍看好多都看不懂,看到后边回过头来看,发现用自旋goroutine实现,真实奇妙;现在想想,除了文章中实现方式,可以用channel同步实现;还可以用sync.WaitGroup实现

    作者回复: 祝贺你升级了;)

    
     2
  • cygnus
    2018-09-19
    demo40的执行结果不是幂等的,程序经常无法正常结束退出,只有极少数几次有正确输出。

    作者回复: 你在win下执行的嘛?

    
     2
  • sky
    2018-09-19
    win64版本:go1.10.2
    linux64版本:go1.11

    linux下实际运行和预期一样,但为何win下会一直运行不会停止呢,且CPU也已经是100% 表示不解呀

    作者回复: 可以加个sleep。

    
     2
  • 冰激凌的眼泪
    2018-09-19
    ‘’否则,我们就先让当前的 goroutine“睡眠”一个纳秒再进入下一个迭代。‘’

    示例代码里没有这个睡眠代码

    作者回复: 代码已经更新了。

    
     2
  • 志鑫
    2019-05-11
    //个人笔记:使用一个通道来控制
    package main

    import "fmt"

    func main() {
        const n = 10
        m := 0
        ch := make(chan int, 10) //通道长度0~10之间,能够影响性能
        for i := 0; i < n; i++ {
            go func(i int) {
                for v := <-ch; v != i; v = <-ch {
                    m++
                    ch <- v //如果不是自己的轮次,则把值再放回去
                }
                fmt.Println(i)
                ch <- i + 1
            }(i)
        }
        ch <- 0
        for v := <-ch; v != 10; v = <-ch {
            ch <- v
        }
        fmt.Println(m)
    }
    展开
    
     1
  • Wind
    2019-03-05
    func main(){
        ch := make(chan struct{})
        for i:=0; i < 100; i++{
            go func(i int){
                fmt.Println(i)
                ch <- struct{}{}
            }(i)
            <-ch
        }
    }
    亲测可以,而且循环的前提是必须上一个通道不阻塞的情况下才能继续,这样的确可以保证按顺序输出
    展开
    
     1
  • SuperP ❤ 飝
    2018-10-11
    runtime.GOMAXPROCS 这个应该能控制P的数量

    作者回复: 对,可以。

    
     1
  • cygnus
    2018-09-20
    “””demo40的执行结果不是幂等的,程序经常无法正常结束退出,只有极少数几次有正确输出。
    2018-09-19
     作者回复
    你在win下执行的嘛?“”“
    是在ubuntu 1804下执行的

    作者回复: demo40.go我小改进了下,你再试试。我在服务器上也没出现你说的这个问题。Go语言很早的版本有可能这样,但是现在肯定不会了。你可联系下极客时间编辑,让她们把你加到群里。

    
     1
  • 蜗牛
    2018-09-19
    func trigger(i int64, fn func()) {
        for {
            //if i != 10 {
            //    fmt.Print("")
            //}

            if count == i {
                fn()
                count += 1
                break
            }
        }
    }

    func main() {
        for i := int64(0); i <= 9; i++ {
            go func(i int64) {
                fn := func() {
                    fmt.Println(i)
                }
                trigger(i, fn)
            }(i)
        }
        trigger(10, func() {})
    }

    取消注释后代码可顺序 0 ~9 输出. 而注释后则会莫名卡主, 怀疑是不是golang runtime 针对这些循环做了些什么, 而且感觉没必要加锁.
    展开

    作者回复: 并发情况下必须利用某种同步工具,否则就不是并发安全的,生产环境中很可能出现不可控的问题。

    
     1
  • timmy21
    2018-09-19
    有一个问题不太清楚,当i和count不相等时,您提到了睡眠1纳秒,可是我没看到有相关的sleep被调用。这是如何做到的?

    作者回复: 代码已经更新了,漏掉了。

    
     1
  • Geek_fcaeb0
    2020-01-28
    老师写的例子相当于用异步的go routine实现了一个全局变量count的递增,并且这些帮助全局变量递增的go routine是按照参数i的顺序依次帮助这个全局变量递增,在轮到i这个go routine时,它递增变量count并且打印当前的count值。这个例子设计非常巧妙,短小的例子涵盖了非常多的知识点。
    
    
我们在线,来聊聊吧