簡體   English   中英

如何在此 Go 頻道為空之前取消訂閱?

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

我不明白為什么這段代碼不起作用:

游樂場 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)
}

啟動時:

  1. 它等待 2 秒
  2. 訂閱經紀人和
  3. 開始接收消息
  4. 在第三條消息之后,我想打破無限for因此go func但我收到一個錯誤:

如果我改變這一行:

if receivedMsgs > 2 {

if receivedMsgs > 3 {

有用。

我認為問題在於,當我調用break時,通道ch中仍有一條消息。

我對嗎?

如何解決這個問題?

我受到https://gist.github.com/maestre3d/4a42e8fa552694f7c97c4811ce913e23 的啟發。

問題是空的 select。 根據這個網站在此處輸入鏈接描述,當使用空的 select 時,select 語句將永遠阻塞,因為沒有可用的 goroutine 可提供任何數據。

為了解決這個問題,我添加了一個帶有取消的上下文,這將允許我在取消訂閱時停止底部 select 並停止第一個 goroutine https://play.golang.org/p/tZklz7iiwGd ,我也會刪除不必要的互斥鎖正在使用通道,這應該是線程安全的。 而且我還讓消息一個接一個地執行,就好像我將兩者都發送到同一個 goroutine 上,這將導致並發,我可以將消息發送到關閉的通道。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM