简体   繁体   English

如何在此 Go 频道为空之前取消订阅?

[英]How to unsubscribe from this Go channel before it is empty?

I do not understand why this code is not working:我不明白为什么这段代码不起作用:

Playground REPL: https://play.golang.org/p/4PrKFnaTeKp游乐场 REPL: https://play.golang.org/p/4PrKFnaTeKp

2009/11/10 23:00:01 published message to 0 subscribers
2009/11/10 23:00:01 published message to 0 subscribers
2009/11/10 23:00:02 client 1 connected
2009/11/10 23:00:02 published message to 1 subscribers
2009/11/10 23:00:02 message received: a message for 1
2009/11/10 23:00:02 receivedMsgs: 1
2009/11/10 23:00:02 message received: a message for all
2009/11/10 23:00:02 receivedMsgs: 2
2009/11/10 23:00:02 published message to 1 subscribers
2009/11/10 23:00:03 published message to 1 subscribers
2009/11/10 23:00:03 message received: a message for 1
2009/11/10 23:00:03 receivedMsgs: 3
2009/11/10 23:00:03 message received: a message for all
2009/11/10 23:00:03 receivedMsgs: 4
2009/11/10 23:00:03 published message to 1 subscribers
2009/11/10 23:00:04 published message to 1 subscribers
2009/11/10 23:00:04 message received: a message for 1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:
main.main()
    /tmp/sandbox948233627/prog.go:70 +0xfb

goroutine 6 [chan send]:
main.(*Broker).Publish(0xc000010240, 0x4c9815, 0x11, 0x0, 0x0)
    /tmp/sandbox948233627/prog.go:121 +0x2af
main.main.func1(0xc000010240)
    /tmp/sandbox948233627/prog.go:34 +0x91
created by main.main
    /tmp/sandbox948233627/prog.go:30 +0xc7

goroutine 7 [semacquire]:
sync.runtime_SemacquireMutex(0xc000018054, 0x0, 0x1)
    /usr/local/go-faketime/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc000018050)
    /usr/local/go-faketime/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
    /usr/local/go-faketime/src/sync/mutex.go:81
main.(*Broker).Unsubscribe(0xc000010240, 0xc000062060)
    /tmp/sandbox948233627/prog.go:93 +0x1c5
main.main.func2(0xc000010240)
    /tmp/sandbox948233627/prog.go:61 +0x1a5
created by main.main
    /tmp/sandbox948233627/prog.go:40 +0xf6
package main

import (
    "fmt"
    "log"
    "sync"
    "time"
)

type Event struct {
    Message  string
    Consumer string
}

func NewEvent(msg string, consumer string) Event {
    return Event{
        Message:  msg,
        Consumer: consumer,
    }
}

type Broker struct {
    consumers map[chan Event]string
    mtx       *sync.Mutex
}

func main() {
    broker := NewBroker()

    go func() {
        for {
            time.Sleep(time.Second * 1)
            broker.Publish(NewEvent("a message for 1", "1"))
            broker.Publish(NewEvent("a message for all", ""))
        }
    }()

    time.Sleep(2 * time.Second)

    go func() {
        ch := broker.Subscribe("1")

        receivedMsgs := 0
        for {
            msg := <-ch
            //---> Here I'm sending message to client's browser
            //if _, err := w.Write([]byte(fmt.Sprintf("data: %s\n\n", msg))); err != nil {
            //  log.Println(err)
            //  return
            //}
            //---> Here I unsubscribe if error in w.Flush() AKA browser was closed
            //if err := w.Flush(); err != nil {
            //log.Println("browser closed")
            //broker.Unsubscribe(ch)
            //return
            //}

            log.Println("message received:", msg.Message)

            if receivedMsgs > 3 {
                broker.Unsubscribe(ch)
                break
            }

            receivedMsgs++
            log.Println("receivedMsgs:", receivedMsgs)
        }
    }()

    select {}
}

func NewBroker() *Broker {
    return &Broker{
        consumers: make(map[chan Event]string),
        mtx:       new(sync.Mutex),
    }
}

func (b *Broker) Subscribe(id string) chan Event {
    b.mtx.Lock()
    defer b.mtx.Unlock()

    c := make(chan Event)
    b.consumers[c] = id

    log.Println(fmt.Sprintf("client %s connected", id))

    return c
}

func (b *Broker) Unsubscribe(c chan Event) {
    b.mtx.Lock()
    defer b.mtx.Unlock()

    id := b.consumers[c]
    close(c)
    delete(b.consumers, c)

    log.Printf("client %s killed, %d remaining\n", id, len(b.consumers))
}

func (b *Broker) Publish(e Event) {
    b.mtx.Lock()
    defer b.mtx.Unlock()

    pubMsg := 0

    for s, id := range b.consumers {
        if e.Consumer != "" {
            // Push to specific consumer
            if id == e.Consumer {
                s <- e
                pubMsg++

                break
            }
        } else {
            // Push to every consumer
            e.Consumer = id
            s <- e
            // Reset unused consumer
            e.Consumer = ""
            pubMsg++
        }
    }

    log.Printf("published message to %d subscribers\n", pubMsg)
}

When you launch:启动时:

  1. it waits 2 second它等待 2 秒
  2. subscribe to broker and订阅经纪人和
  3. start getting messages开始接收消息
  4. after the third message I wanna break the infinite for and hence the go func but I get an error:在第三条消息之后,我想打破无限for因此go func但我收到一个错误:

If I change the line from this:如果我改变这一行:

if receivedMsgs > 2 {

to

if receivedMsgs > 3 {

it works.有用。

I think the problem is because when I call break there is still a message in channel ch .我认为问题在于,当我调用break时,通道ch中仍有一条消息。

Am I right?我对吗?

How to fix this?如何解决这个问题?

I was inspired by https://gist.github.com/maestre3d/4a42e8fa552694f7c97c4811ce913e23.我受到https://gist.github.com/maestre3d/4a42e8fa552694f7c97c4811ce913e23 的启发。

The problem is the empty select.问题是空的 select。 According to this website enter link description here when an empty select is used the select statement blocks forever since no goroutine is available to provide any data.根据这个网站在此处输入链接描述,当使用空的 select 时,select 语句将永远阻塞,因为没有可用的 goroutine 可提供任何数据。

To solve this I added a context with cancel which will allow me to when I unsubscribe i will stop the bottom select and stop the first goroutine https://play.golang.org/p/tZklz7iiwGd also, i remove the unnecessary mutex as you are using channels and this should be thread safe.为了解决这个问题,我添加了一个带有取消的上下文,这将允许我在取消订阅时停止底部 select 并停止第一个 goroutine https://play.golang.org/p/tZklz7iiwGd ,我也会删除不必要的互斥锁正在使用通道,这应该是线程安全的。 And I also put the msgs to execute one by one as if i put both to be sent on the same goroutine this will cause a concurrency where i can send a msg to a closed channel.而且我还让消息一个接一个地执行,就好像我将两者都发送到同一个 goroutine 上,这将导致并发,我可以将消息发送到关闭的通道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM