我正在尝试从接收方端实现优雅的频道关闭。
是的,我知道这违反了频道关闭规则:
但我想实现这样的逻辑。不幸的是,我在多个案例中遇到了死锁问题:应用程序只是挂起无限时间,试图再次锁定同一个锁定的Mutex
。
所以,我有2个goroutines:
我的频道包含在sync.Mutex
和closed
布尔标志的结构中:
type Chan struct {
sync.Mutex // can be replaced with deadlock.Mutex from "github.com/sasha-s/go-deadlock"
data chan int
closed bool
}
此结构上的所有Send()
,Close()
,IsClosed()
操作都使用Mutex
进行保护,并防止重复锁定具有非线程安全方法版本(send()
,close()
,isClosed()
)。
完整的源代码:
package main
import (
"log"
"net/http"
"sync"
)
func main() {
log.Println("Start")
ch := New(0) // unbuffered channel to expose problem faster
wg := sync.WaitGroup{}
wg.Add(2)
// send data:
go func(ch *Chan) {
for i := 0; i < 100; i++ {
ch.Send(i)
}
wg.Done()
}(ch)
// receive data and close from receiver side:
go func(ch *Chan) {
for data := range ch.data {
log.Printf("Received %d data", data)
// Bad practice: I want to close the channel from receiver's side:
if data > 50 {
ch.Close()
break
}
}
wg.Done()
}(ch)
wg.Wait()
log.Println("End")
}
type Chan struct {
deadlock.Mutex //sync.Mutex
data chan int
closed bool
}
func New(size int) *Chan {
defer func() {
log.Printf("Channel was created")
}()
return &Chan{
data: make(chan int, size),
}
}
func (c *Chan) Send(data int) {
c.Lock()
c.send(data)
c.Unlock()
}
func (c *Chan) Close() {
c.Lock()
c.close()
c.Unlock()
}
func (c *Chan) IsClosed() bool {
c.Lock()
defer c.Unlock()
return c.isClosed()
}
// send is internal non-threadsafe api.
func (c *Chan) send(data int) {
if !c.closed {
c.data <- data
log.Printf("Data %d was sent", data)
}
}
// close is internal non-threadsafe api.
func (c *Chan) close() {
if !c.closed {
close(c.data)
c.closed = true
log.Println("Channel was closed")
} else {
log.Println("Channel was already closed")
}
}
// isClosed is internal non-threadsafe api.
func (c *Chan) isClosed() bool {
return c.closed
}
您可以在sandbox。
中运行此程序在本地计算机上,在少量运行中,30秒后输出将是(使用deadlock.Mutex
而不是sync.Mutex
):
2018/04/01 11:26:22 Data 50 was sent
2018/04/01 11:26:22 Received 50 data
2018/04/01 11:26:22 Data 51 was sent
2018/04/01 11:26:22 Received 51 data
POTENTIAL DEADLOCK:
Previous place where the lock was grabbed
goroutine 35 lock 0xc42015a040
close-from-receiver-side/closeFromReceiverSideIsBadPractice.go:71 main.(*Chan).Send { c.Lock() } <<<<<
close-from-receiver-side/closeFromReceiverSideIsBadPractice.go:30 main.main.func1 { ch.Send(i) }
Have been trying to lock it again for more than 30s
goroutine 36 lock 0xc42015a040
close-from-receiver-side/closeFromReceiverSideIsBadPractice.go:77 main.(*Chan).Close { c.Lock() } <<<<<
close-from-receiver-side/closeFromReceiverSideIsBadPractice.go:44 main.main.func2 { ch.Close() }
为什么会发生此死锁以及如何修复此实现以避免死锁?
关闭发送方的频道不是答案。所以,这不是我的问题的解决方法:Example of closing channel from sender side。
答案 0 :(得分:2)
发送抓取锁,然后尝试沿通道发送数据。这可能发生在第50次接收操作之后。没有更多的接收,所以c.data <- data
永远阻止,因此互斥锁永远存在。
要取消,请使用另一个通道(而不是布尔值)和Send中的select语句。您还可以使用the context package。
答案 1 :(得分:1)
您可以尽情地尝试:您必须从发送方关闭频道。
你可以在没有完全锁定的情况下让它工作,但你会泄漏goroutines。发件人将永远阻止,无法关闭。如果接收器想要触发关闭,它必须告诉发送器关闭通道。如何告诉发件人关闭:
stop
- 频道,当关闭时发信号通知发送方关闭数据频道(无法多次关闭)ctx.Context
:调用cancel()
函数会发信号通知发件人停止。 (可以多次取消,无需担心)(仅详细说明彼得斯正确答案)