关闭来自接收方的频道:从多个goroutines访问sync.Mutex时出现死锁

时间:2018-04-01 08:43:31

标签: go mutex deadlock channel

我正在尝试从接收方端实现优雅的频道关闭

是的,我知道这违反了频道关闭规则:

  

... don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders.

但我想实现这样的逻辑。不幸的是,我在多个案例中遇到了死锁问题:应用程序只是挂起无限时间,试图再次锁定同一个锁定的Mutex

所以,我有2个goroutines:

  • 将写入频道和
  • 的人
  • 另一个将接收数据+将从接收方关闭频道。

我的频道包含在sync.Mutexclosed布尔标志的结构中:

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

2 个答案:

答案 0 :(得分:2)

发送抓取锁,然后尝试沿通道发送数据。这可能发生在第50次接收操作之后。没有更多的接收,所以c.data <- data永远阻止,因此互斥锁永远存在。

要取消,请使用另一个通道(而不是布尔值)和Send中的select语句。您还可以使用the context package

答案 1 :(得分:1)

您可以尽情地尝试:您必须从发送方关闭频道。

你可以在没有完全锁定的情况下让它工作,但你会泄漏goroutines。发件人将永远阻止,无法关闭。如果接收器想要触发关闭,它必须告诉发送器关闭通道。如何告诉发件人关闭:

  • 您建议的布尔值(需要另一个互斥锁)
  • 一个stop - 频道,当关闭时发信号通知发送方关闭数据频道(无法多次关闭)
  • a ctx.Context:调用cancel()函数会发信号通知发件人停止。 (可以多次取消,无需担心)

(仅详细说明彼得斯正确答案)