2016-05-13 61 views
4

这是我正在生成死锁的程序,我该如何避免它,以及处理这种情况的推荐模式是什么。如何避免这个golang程序中的死锁?

问题是超时后如何检测我的频道上没有阅读器?

var wg sync.WaitGroup 

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
    select { 
    case x := <-c: 
     fmt.Println("Read", x) 
    case <-ti: 
     fmt.Println("TIMED OUT") 
    } 
    wg.Done() 
} 

回答

1

您有一个无缓冲的频道。根据docs

如果信道是无缓冲,发送器块,直到接收器已经接收到 的值。如果信道具有缓冲器,发送方块 仅直到该值已经被拷贝到缓冲区

通过改变信道到被缓冲,我们能够避免死锁。

c := make(chan int, 10) // holds 10 ints 

我也建议您阅读https://golang.org/doc/effective_go.html#channels,这里有一些与渠道有关的好东西。

6

因此,让我们看看你的来源真正发生了什么。你有两个 goroutines(有两个以上,但我们将专注于明确的),mainreadFromChannel

让我们看看什么readFromChannel做:

if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group. 
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group. 

现主营:

adds to waitgroup 
make a channel `c` 
start a goroutine `readFromChannel` 
sleep for 5 seconds 
send 10 to channel `c` 
call wait for waitgroup 

现在,让我们去通过执行您的代码流,同时(你的代码可能/可能不会执行以此顺序每次都记住)

1) wg.Add(1) 
2) c := make(chan int) 
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
#timer ti starts# 
4) time.Sleep(time.Duration(5) * time.Second) 
#MAIN Goroutine begins sleep 
#timer ti expires# 
5) case <-ti: 
6) fmt.Println("TIMED OUT") 
7) wg.Done() 
# readFromChannel Goroutine returns # 
#MAIN Goroutine exits sleep# 
8) c<-10 
9) ......#DEADLOCK# 

现在你可以gue ss为什么你会陷入僵局。在进行中,无缓冲通道将阻止,直到通道的另一端发生某些事情,无论您是发送还是接收。因此c <- 10会阻塞,直到从c的另一端读取某些内容为止,但您为此所用的参数在2秒前已经退出画面。因此,c永远是块,并且因为main是最后一个goroutine,你会得到一个死锁。

如何预防?使用频道时,请确保每个send频道的另一端总是有一个receive。您也可以使用缓冲通道,但在上面的代码中,它不会是“正确的”解决方案。

这里是我的僵局的解决办法:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
     // the forloop will run forever 
    loop: // ** 
    for { 
     select { 
      case x := <-c: 
        fmt.Println("Read", x) 
        break loop // breaks out of the for loop and the select ** 
      case <-ti: 
        fmt.Println("TIMED OUT") 
      } 
    } 
    wg.Done() 
} 

** see this answer for details

+0

这是我在一段时间内阅读最有用的答案之一。只是为了确保我遵循:“修复”的作品,因为它保持接收器频道即使超时。所以'wg.Done()'(并终止''main'例程)只会在从'c'读入内容时才会发生,对吧? –

+1

没错,但为了清理一下,它保持** goroutine **运行。 – AJPennster

0

你的问题是,你正在使用select声明,但没有使用一个够程内。

go func() { 
    for { 
     select { 
     case x := <-c: 
      fmt.Println("Read", x) 
     case <-ti: 
      fmt.Println("TIMED OUT") 
     } 
    } 
}() 

走出不同的并发执行够程的值可以选择关键字,它非常类似于开关控制语句,有时也被称为通信开关来完成。

在默认情况下在select语句中使用发送操作可以保证发送是非阻塞的!如果没有这种情况,选择将永远执行。

https://play.golang.org/p/Ai1ggveb4s

0

这是一个老问题,但我潜水深入学习渠道自己,发现这个在这里。

我想你只需要在完成发送后关闭频道?

代码:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    close(c) // <- CLOSE IT HERE 
    wg.Wait() 
}