multithreading - 尝试两次接收值时出现死锁

标签 multithreading go

我看了关于 Advanced Go Concurrency Patterns 的精彩视频.一开始,Sameer Ajmani 展示了一个乒乓应用程序。

package main

import (
    "fmt"
    "time"
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    fmt.Println(<-table) // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

代码是如何工作的,我了解 90%。它们是两个 goroutine,它们在主线程休眠期间互相发送消息 ping 和 pong。

然后我尝试跟随

package main

import (
    "fmt"
    "time"
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    fmt.Println(<-table) // game over; grab the ball
    fmt.Println(<-table) // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

我在这里陷入了僵局,真的不明白为什么。查看 go 例程中的最后一行,我尝试像倒数第二行一样从 channel 接收值。在后台,两个 goroutines 仍然继续循环并互相发送值。对我来说,它似乎是一个用于表变量 channel 的多接收器。

我的主要问题是,我在第二个样本中遇到了什么僵局?

最佳答案

In background the two goroutines still continue loop and send to each other value.

不,他们没有。

当您使用 make(chan *Ball) 创建 channel 时,你正在制作一个无缓冲的 channel 。这相当于说 make(chan *Ball,0) ,这意味着该 channel 可以容纳 0 个东西 - 或者,更明确地说,对 channel 的任何写入都将阻塞,直到另一个例程从该 channel 读取,反之亦然。

无缓冲 channel 的执行顺序是这样的:

  • 玩家“ping”已创建,尝试从 ball := <-table 的表中读取, 直到 table 才被阻止写给
  • 玩家“pong”已创建,尝试使用 ball := <-table 从表中读取, 直到 table 才被阻止写给
  • Main 将 Ball 写入 table使用以下行:

     table <- new(Ball) // game on; toss the ball
    

    这没有被屏蔽,因为有人在 channel 上等待阅读。

  • 现在 ping 读取球(在 ping 的 ball := <-table 之后继续执行)
  • Ping 用table <- ball 把球放在 table 上, 未被阻止,因为 pong 正在等待
  • Pong 读球(在 pong 的 ball := <-table 之后继续执行)
  • Pong 用 table <- ball 把球放在 table 上, 未被阻止,因为 ping 正在等待
  • Ping 读取球(在 ping 的 ball := <-table 之后继续执行)
  • ....等
    • 直到 main 通过阅读球而不是其中一名球员结束比赛

这是使用 channel 确保一次只有一个例程运行的一个很好的例子。

为了结束游戏,主线程在一秒后将球从 channel 中抢走:

 time.Sleep(1 * time.Second)
 fmt.Println(<-table) // game over; grab the ball

在此之后,table channel 将是空的,任何进一步的读取都将被阻塞。

  • 此时,player例程在 ball := <- table 处被阻止.

如果你再做一次<-table在主线程中读取, 读取也会阻塞,直到例程尝试写入表 channel 。但是,由于没有其他例程在运行,因此出现死锁(所有 goroutine 都被阻塞)。


好的。那我可以把两个球放在 channel 里吗?

没有。

如果我们尝试将两个球放入 channel 中,我们可能会得到输出:

Ping 1
Pong 1

因为两者都是player试图将他们的球放回 channel 中的例程将被卡住 - 但没有人会试图阅读它。订单看起来像这样:

  • 播放器“ping”已创建,尝试从表中读取,已阻止
  • 玩家“pong”已创建,尝试从表中读取,已被阻止
  • main 将第一个球放入 table (没有因为有人在等待阅读而被阻止)
  • main 将第二个球放入 table (没有因为有人在等待阅读而被阻止)
  • 平读第一个球
  • Pong读第二个球
  • Ping 将第一个球放在 table 上,因为没有人在等待阅读而被挡住了
  • Pong 将第二个球放在 table 上,因为没有人等着看而被挡住了
  • 两个玩家都被阻止了
    • 直到 main 通过读取两个球结束游戏。

Here's a program to illustrate

正如评论者所指出的,结束游戏的更好方法是关闭 channel 。但是,我希望这个讨论已经消除了您的困惑。

关于multithreading - 尝试两次接收值时出现死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26009501/

相关文章:

go - mongo-go-driver 中的自定义 UnmarshalBSON

go - panic : runtime error: invalid memory address or nil pointer dereference only on the GAE

iOS关闭后台线程

c - 服务器从多个客户端接收数据并在Linux中使用C通过线程同时存储在单独的文件中

Java - 为什么这个基本的 ticking 类会占用这么多 cpu?

go - 声明递归/多维映射

multithreading - await Task.WhenAll() 与 Task.WhenAll().Wait()

c++ - 在多线程 C++ 应用程序中,我是否需要互斥锁来保护简单的 boolean 值?

html - 为什么 html/template 不显示所有 html 条件注释?

http - 在 Go 中跟踪 HTTP 请求时指定超时