go - 问题理解Golang并发代码

标签 go concurrency channels

我刚开始学习golang,在进行并发时,我不小心编写了以下代码:


import ( 
    "fmt"
)

func squares(c chan int) {
    for i := 0; i < 4; i++ {
        num := <- c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main start")
    
    c := make(chan int)
    
    go squares(c)
    
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    
    go squares(c)
    
    c <- 5
    c <- 6
    c <- 7
    c <- 8
    
    fmt.Println("main stop")
}
最初我应该分配c := make(chan int, 3)
我无法理解所编写代码的输出。
main start
1
4
9
25
36
49
16
main stop
我想了解代码是如何执行的。
我期望发生错误:所有goroutine都在睡着-死锁!
非常感谢!

最佳答案

我不确定您是否真正想要实现什么,尤其是那个怪异循环的原因:

    for i := 0; i < 4; i++ {
        num := <- c
        fmt.Println(num * num)
    }
但不管怎么说。
首先,对 channel 和goroutine的工作原理进行一些解释。
channel 是线程安全的消息传递管道,用于在不同的执行上下文之间共享数据。使用make指令创建 channel ,然后
c := make(chan int, 3)
意味着创建一个int类型的 channel ,其“缓冲区”大小为3。要理解此元素非常重要。 channel 确实遵循以该规则为基础的生产者/消费者模式:
对于生产者:
  • 如果我尝试在具有“可用空间”的 channel 中推送某些数据,则不会阻塞并且执行下一条指令
  • 如果我尝试在没有“可用空间”的情况下在 channel 中推送一些数据,它将阻塞直到之前的内容被处理为止

    对于消费者:
  • 如果我尝试从空 channel 中获取元素,它将阻塞,直到有一些数据出现为止
  • 如果我尝试从非空 channel 中获取元素,它将采用第一个( channel 为FIFO)

  • 请注意,可以使用select指令提供的某些特殊模式将所有阻止操作都变为非阻止,但这是另一个主题:)。
    Goroutine是轻型子流程,例程(无线程)。这里没有地方进行详细解释,但是重要的是1个goroutine === 1个执行栈。
    因此,在您的情况下,只有一个消费者之前,输出是可以预测的。一旦启动第二个Goroutine,消耗顺序仍然是可预测的( channel 的大小只有一个),但是执行顺序却不可!
    值得注意的是:您只有7个输出...这是因为您的主goroutine在它发生之前就结束了,从而终止了整个程序的执行。那一点说明了为什么没有all goroutines are asleep - deadlock!
    如果您想拥有它,您只需要在main的末尾添加<- c即可:
    package main
    
    import (
        "fmt"
    )
    
    func squares(c chan int) {
        for i := 0; i < 4; i++ {
            num := <-c
            fmt.Println(num * num)
        }
    }
    
    func main() {
        fmt.Println("main start")
    
        c := make(chan int)
    
        go squares(c)
    
        c <- 1
        c <- 2
        c <- 3
        c <- 4
    
        go squares(c)
    
        c <- 5
        c <- 6
        c <- 7
        c <- 8
    
        <-c
        fmt.Println("main stop")
    }
    
    
    您将拥有我认为您期望的行为:
    main start
    1
    4
    9
    25
    36
    49
    64
    16
    fatal error: all goroutines are asleep - deadlock!
    
    编辑:逐步执行:
        // a goroutine is created and c is empty. because
        // the code of `squares` act as a consumer, the goroutine
        // "block" at instruction `num := <-c`, but not the main goroutine
        go squares(c)
        
        // 1 is pushed to the channel. Till "c" is not full the main goroutine
        // doesn't block but the other goroutine may be "released"
        c <- 1
    
        // if goroutine of squares has not consume 1 yet, main goroutine block   
        // untill so, but is released just after
        c <- 2
    
        // it continues with same logic
        c <- 3
        c <- 4
    
        // till main goroutine encountered `<- c` (instruction I added) .
        // Here, it behave as a consumer of "c". At this point all
        // goroutine are waiting as consuler on "c" => deadlock
    

    关于go - 问题理解Golang并发代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62875248/

    相关文章:

    Beego Controller 中的 JSON 响应

    Java RMI 和同步方法

    在尝试发送之前进行非阻塞 channel 发送,测试是否失败?

    python - 在 Heroku 上部署 asgi 和 wsgi

    C++ 风格的 Golang 迭代器

    将提示与 CountDocument 一起使用时,MongoDB golang 驱动程序不会返回无法识别的字段 `hint`

    go - 从列表中删除客户端会终止其他连接

    python - concurrent.futures 的状态并未反射(reflect)其真实状态

    multithreading - 在并发编程的上下文中, "data races"和 "race condition"实际上是同一件事吗?

    multithreading - 有多个 channel 与单个共享结构进行通信是否线程安全?